singletonList(draft));
+ }
+
+ @Override
+ public void onOpen(WebSocket conn, ClientHandshake handshake) {
+ conn.send("Welcome to the server!"); //This method sends a message to the new client
+ broadcast("new connection: " + handshake
+ .getResourceDescriptor()); //This method sends a message to all clients connected
+ System.out.println(
+ conn.getRemoteSocketAddress().getAddress().getHostAddress() + " entered the room!");
+ }
- public ChatServer( InetSocketAddress address ) {
- super( address );
- }
+ @Override
+ public void onClose(WebSocket conn, int code, String reason, boolean remote) {
+ broadcast(conn + " has left the room!");
+ System.out.println(conn + " has left the room!");
+ }
- @Override
- public void onOpen( WebSocket conn, ClientHandshake handshake ) {
- conn.send("Welcome to the server!"); //This method sends a message to the new client
- broadcast( "new connection: " + handshake.getResourceDescriptor() ); //This method sends a message to all clients connected
- System.out.println( conn.getRemoteSocketAddress().getAddress().getHostAddress() + " entered the room!" );
- }
+ @Override
+ public void onMessage(WebSocket conn, String message) {
+ broadcast(message);
+ System.out.println(conn + ": " + message);
+ }
- @Override
- public void onClose( WebSocket conn, int code, String reason, boolean remote ) {
- broadcast( conn + " has left the room!" );
- System.out.println( conn + " has left the room!" );
- }
+ @Override
+ public void onMessage(WebSocket conn, ByteBuffer message) {
+ broadcast(message.array());
+ System.out.println(conn + ": " + message);
+ }
- @Override
- public void onMessage( WebSocket conn, String message ) {
- broadcast( message );
- System.out.println( conn + ": " + message );
- }
- @Override
- public void onMessage( WebSocket conn, ByteBuffer message ) {
- broadcast( message.array() );
- System.out.println( conn + ": " + message );
- }
+ public static void main(String[] args) throws InterruptedException, IOException {
+ int port = 8887; // 843 flash policy port
+ try {
+ port = Integer.parseInt(args[0]);
+ } catch (Exception ex) {
+ }
+ ChatServer s = new ChatServer(port);
+ s.start();
+ System.out.println("ChatServer started on port: " + s.getPort());
- public static void main( String[] args ) throws InterruptedException , IOException {
- WebSocketImpl.DEBUG = true;
- int port = 8887; // 843 flash policy port
- try {
- port = Integer.parseInt( args[ 0 ] );
- } catch ( Exception ex ) {
- }
- ChatServer s = new ChatServer( port );
- s.start();
- System.out.println( "ChatServer started on port: " + s.getPort() );
+ BufferedReader sysin = new BufferedReader(new InputStreamReader(System.in));
+ while (true) {
+ String in = sysin.readLine();
+ s.broadcast(in);
+ if (in.equals("exit")) {
+ s.stop(1000);
+ break;
+ }
+ }
+ }
- BufferedReader sysin = new BufferedReader( new InputStreamReader( System.in ) );
- while ( true ) {
- String in = sysin.readLine();
- s.broadcast( in );
- if( in.equals( "exit" ) ) {
- s.stop(1000);
- break;
- }
- }
- }
- @Override
- public void onError( WebSocket conn, Exception ex ) {
- ex.printStackTrace();
- if( conn != null ) {
- // some errors like port binding failed may not be assignable to a specific websocket
- }
- }
+ @Override
+ public void onError(WebSocket conn, Exception ex) {
+ ex.printStackTrace();
+ if (conn != null) {
+ // some errors like port binding failed may not be assignable to a specific websocket
+ }
+ }
- @Override
- public void onStart() {
- System.out.println("Server started!");
- }
+ @Override
+ public void onStart() {
+ System.out.println("Server started!");
+ setConnectionLostTimeout(0);
+ setConnectionLostTimeout(100);
+ }
}
diff --git a/src/main/example/ChatServerAttachmentExample.java b/src/main/example/ChatServerAttachmentExample.java
index 378cc2cf7..d05a7781d 100644
--- a/src/main/example/ChatServerAttachmentExample.java
+++ b/src/main/example/ChatServerAttachmentExample.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -23,91 +23,92 @@
* OTHER DEALINGS IN THE SOFTWARE.
*/
-import org.java_websocket.WebSocket;
-import org.java_websocket.WebSocketImpl;
-import org.java_websocket.framing.Framedata;
-import org.java_websocket.handshake.ClientHandshake;
-import org.java_websocket.server.WebSocketServer;
-
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
+import org.java_websocket.WebSocket;
+import org.java_websocket.handshake.ClientHandshake;
+import org.java_websocket.server.WebSocketServer;
/**
* A simple WebSocketServer implementation. Keeps track of a "chatroom".
- *
+ *
* Shows how to use the attachment for a WebSocket. This example just uses a simple integer as ID.
* Setting an attachment also works in the WebSocketClient
*/
public class ChatServerAttachmentExample extends WebSocketServer {
- Integer index = 0;
- public ChatServerAttachmentExample( int port ) throws UnknownHostException {
- super( new InetSocketAddress( port ) );
- }
+ Integer index = 0;
+
+ public ChatServerAttachmentExample(int port) throws UnknownHostException {
+ super(new InetSocketAddress(port));
+ }
+
+ public ChatServerAttachmentExample(InetSocketAddress address) {
+ super(address);
+ }
+
+ @Override
+ public void onOpen(WebSocket conn, ClientHandshake handshake) {
+ conn.setAttachment(index); //Set the attachment to the current index
+ index++;
+ // Get the attachment of this connection as Integer
+ System.out.println(
+ conn.getRemoteSocketAddress().getAddress().getHostAddress() + " entered the room! ID: "
+ + conn.getAttachment());
+ }
- public ChatServerAttachmentExample( InetSocketAddress address ) {
- super( address );
- }
+ @Override
+ public void onClose(WebSocket conn, int code, String reason, boolean remote) {
+ // Get the attachment of this connection as Integer
+ System.out.println(conn + " has left the room! ID: " + conn.getAttachment());
+ }
- @Override
- public void onOpen( WebSocket conn, ClientHandshake handshake ) {
- conn.setAttachment( index ); //Set the attachment to the current index
- index++;
- // Get the attachment of this connection as Integer
- System.out.println( conn.getRemoteSocketAddress().getAddress().getHostAddress() + " entered the room! ID: " + conn.getAttachment() );
- }
+ @Override
+ public void onMessage(WebSocket conn, String message) {
+ System.out.println(conn + ": " + message);
+ }
- @Override
- public void onClose( WebSocket conn, int code, String reason, boolean remote ) {
- // Get the attachment of this connection as Integer
- System.out.println( conn + " has left the room! ID: " + conn.getAttachment() );
- }
+ @Override
+ public void onMessage(WebSocket conn, ByteBuffer message) {
+ System.out.println(conn + ": " + message);
+ }
- @Override
- public void onMessage( WebSocket conn, String message ) {
- System.out.println( conn + ": " + message );
- }
- @Override
- public void onMessage( WebSocket conn, ByteBuffer message ) {
- System.out.println( conn + ": " + message );
- }
+ public static void main(String[] args) throws InterruptedException, IOException {
+ int port = 8887; // 843 flash policy port
+ try {
+ port = Integer.parseInt(args[0]);
+ } catch (Exception ex) {
+ }
+ ChatServerAttachmentExample s = new ChatServerAttachmentExample(port);
+ s.start();
+ System.out.println("ChatServer started on port: " + s.getPort());
- public static void main( String[] args ) throws InterruptedException , IOException {
- WebSocketImpl.DEBUG = true;
- int port = 8887; // 843 flash policy port
- try {
- port = Integer.parseInt( args[ 0 ] );
- } catch ( Exception ex ) {
- }
- ChatServerAttachmentExample s = new ChatServerAttachmentExample( port );
- s.start();
- System.out.println( "ChatServer started on port: " + s.getPort() );
+ BufferedReader sysin = new BufferedReader(new InputStreamReader(System.in));
+ while (true) {
+ String in = sysin.readLine();
+ s.broadcast(in);
+ if (in.equals("exit")) {
+ s.stop(1000);
+ break;
+ }
+ }
+ }
- BufferedReader sysin = new BufferedReader( new InputStreamReader( System.in ) );
- while ( true ) {
- String in = sysin.readLine();
- s.broadcast( in );
- if( in.equals( "exit" ) ) {
- s.stop(1000);
- break;
- }
- }
- }
- @Override
- public void onError( WebSocket conn, Exception ex ) {
- ex.printStackTrace();
- if( conn != null ) {
- // some errors like port binding failed may not be assignable to a specific websocket
- }
- }
+ @Override
+ public void onError(WebSocket conn, Exception ex) {
+ ex.printStackTrace();
+ if (conn != null) {
+ // some errors like port binding failed may not be assignable to a specific websocket
+ }
+ }
- @Override
- public void onStart() {
- System.out.println("Server started!");
- }
+ @Override
+ public void onStart() {
+ System.out.println("Server started!");
+ }
}
diff --git a/src/main/example/CustomHeaderClientExample.java b/src/main/example/CustomHeaderClientExample.java
index 073cb14f5..e35012968 100644
--- a/src/main/example/CustomHeaderClientExample.java
+++ b/src/main/example/CustomHeaderClientExample.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -23,8 +23,6 @@
* OTHER DEALINGS IN THE SOFTWARE.
*/
-import org.java_websocket.WebSocketImpl;
-
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
@@ -32,40 +30,39 @@
/**
* This class shows how to add additional http header like "Origin" or "Cookie".
- *
+ *
* To see it working, start ServerRejectHandshakeExample and then start this example.
*/
public class CustomHeaderClientExample {
- public static void main( String[] args ) throws URISyntaxException, InterruptedException {
- WebSocketImpl.DEBUG = true;
- Map httpHeaders = new HashMap();
- httpHeaders.put( "Cookie", "test" );
- ExampleClient c = new ExampleClient( new URI( "ws://localhost:8887" ), httpHeaders);
- //We expect no successful connection
- c.connectBlocking();
- httpHeaders.put( "Cookie", "username=nemo" );
- c = new ExampleClient( new URI( "ws://localhost:8887" ) , httpHeaders);
- //Wer expect a successful connection
- c.connectBlocking();
- c.closeBlocking();
- httpHeaders.put( "Access-Control-Allow-Origin", "*" );
- c = new ExampleClient( new URI( "ws://localhost:8887" ) , httpHeaders);
- //We expect no successful connection
- c.connectBlocking();
- c.closeBlocking();
- httpHeaders.clear();
- httpHeaders.put( "Origin", "localhost:8887" );
- httpHeaders.put( "Cookie", "username=nemo" );
- c = new ExampleClient( new URI( "ws://localhost:8887" ) , httpHeaders);
- //We expect a successful connection
- c.connectBlocking();
- c.closeBlocking();
- httpHeaders.clear();
- httpHeaders.put( "Origin", "localhost" );
- httpHeaders.put( "cookie", "username=nemo" );
- c = new ExampleClient( new URI( "ws://localhost:8887" ) , httpHeaders);
- //We expect no successful connection
- c.connectBlocking();
- }
+ public static void main(String[] args) throws URISyntaxException, InterruptedException {
+ Map httpHeaders = new HashMap();
+ httpHeaders.put("Cookie", "test");
+ ExampleClient c = new ExampleClient(new URI("ws://localhost:8887"), httpHeaders);
+ //We expect no successful connection
+ c.connectBlocking();
+ httpHeaders.put("Cookie", "username=nemo");
+ c = new ExampleClient(new URI("ws://localhost:8887"), httpHeaders);
+ //Wer expect a successful connection
+ c.connectBlocking();
+ c.closeBlocking();
+ httpHeaders.put("Access-Control-Allow-Origin", "*");
+ c = new ExampleClient(new URI("ws://localhost:8887"), httpHeaders);
+ //We expect no successful connection
+ c.connectBlocking();
+ c.closeBlocking();
+ httpHeaders.clear();
+ httpHeaders.put("Origin", "localhost:8887");
+ httpHeaders.put("Cookie", "username=nemo");
+ c = new ExampleClient(new URI("ws://localhost:8887"), httpHeaders);
+ //We expect a successful connection
+ c.connectBlocking();
+ c.closeBlocking();
+ httpHeaders.clear();
+ httpHeaders.put("Origin", "localhost");
+ httpHeaders.put("cookie", "username=nemo");
+ c = new ExampleClient(new URI("ws://localhost:8887"), httpHeaders);
+ //We expect no successful connection
+ c.connectBlocking();
+ }
}
diff --git a/src/main/example/ExampleClient.java b/src/main/example/ExampleClient.java
index 7b1bafc0f..73b832bea 100644
--- a/src/main/example/ExampleClient.java
+++ b/src/main/example/ExampleClient.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -26,55 +26,58 @@
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Map;
-
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.drafts.Draft;
-import org.java_websocket.drafts.Draft_6455;
-import org.java_websocket.framing.Framedata;
import org.java_websocket.handshake.ServerHandshake;
-/** This example demonstrates how to create a websocket connection to a server. Only the most important callbacks are overloaded. */
+/**
+ * This example demonstrates how to create a websocket connection to a server. Only the most
+ * important callbacks are overloaded.
+ */
public class ExampleClient extends WebSocketClient {
- public ExampleClient( URI serverUri , Draft draft ) {
- super( serverUri, draft );
- }
+ public ExampleClient(URI serverUri, Draft draft) {
+ super(serverUri, draft);
+ }
- public ExampleClient( URI serverURI ) {
- super( serverURI );
- }
+ public ExampleClient(URI serverURI) {
+ super(serverURI);
+ }
- public ExampleClient( URI serverUri, Map httpHeaders ) {
- super(serverUri, httpHeaders);
- }
+ public ExampleClient(URI serverUri, Map httpHeaders) {
+ super(serverUri, httpHeaders);
+ }
- @Override
- public void onOpen( ServerHandshake handshakedata ) {
- send("Hello, it is me. Mario :)");
- System.out.println( "opened connection" );
- // if you plan to refuse connection based on ip or httpfields overload: onWebsocketHandshakeReceivedAsClient
- }
+ @Override
+ public void onOpen(ServerHandshake handshakedata) {
+ send("Hello, it is me. Mario :)");
+ System.out.println("opened connection");
+ // if you plan to refuse connection based on ip or httpfields overload: onWebsocketHandshakeReceivedAsClient
+ }
- @Override
- public void onMessage( String message ) {
- System.out.println( "received: " + message );
- }
+ @Override
+ public void onMessage(String message) {
+ System.out.println("received: " + message);
+ }
- @Override
- public void onClose( int code, String reason, boolean remote ) {
- // The codecodes are documented in class org.java_websocket.framing.CloseFrame
- System.out.println( "Connection closed by " + ( remote ? "remote peer" : "us" ) + " Code: " + code + " Reason: " + reason );
- }
+ @Override
+ public void onClose(int code, String reason, boolean remote) {
+ // The close codes are documented in class org.java_websocket.framing.CloseFrame
+ System.out.println(
+ "Connection closed by " + (remote ? "remote peer" : "us") + " Code: " + code + " Reason: "
+ + reason);
+ }
- @Override
- public void onError( Exception ex ) {
- ex.printStackTrace();
- // if the error is fatal then onClose will be called additionally
- }
+ @Override
+ public void onError(Exception ex) {
+ ex.printStackTrace();
+ // if the error is fatal then onClose will be called additionally
+ }
- public static void main( String[] args ) throws URISyntaxException {
- ExampleClient c = new ExampleClient( new URI( "ws://localhost:8887" )); // more about drafts here: http://github.com/TooTallNate/Java-WebSocket/wiki/Drafts
- c.connect();
- }
+ public static void main(String[] args) throws URISyntaxException {
+ ExampleClient c = new ExampleClient(new URI(
+ "ws://localhost:8887")); // more about drafts here: http://github.com/TooTallNate/Java-WebSocket/wiki/Drafts
+ c.connect();
+ }
-}
\ No newline at end of file
+}
diff --git a/src/main/example/FragmentedFramesExample.java b/src/main/example/FragmentedFramesExample.java
index f6b677e0d..dcf658553 100644
--- a/src/main/example/FragmentedFramesExample.java
+++ b/src/main/example/FragmentedFramesExample.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -29,54 +29,56 @@
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
-
import org.java_websocket.WebSocket;
import org.java_websocket.client.WebSocketClient;
-import org.java_websocket.drafts.Draft_6455;
-import org.java_websocket.framing.Framedata.Opcode;
+import org.java_websocket.enums.Opcode;
/**
- * This example shows how to send fragmented frames.
- * For information on when to used fragmented frames see http://tools.ietf.org/html/rfc6455#section-5.4
- * Fragmented and normal messages can not be mixed.
- * One is however allowed to mix them with control messages like ping/pong.
- *
+ * This example shows how to send fragmented frames.
For information on when to used fragmented
+ * frames see http://tools.ietf.org/html/rfc6455#section-5.4
Fragmented and normal messages can
+ * not be mixed. One is however allowed to mix them with control messages like ping/pong.
+ *
* @see WebSocket#sendFragmentedFrame(Opcode, ByteBuffer, boolean)
**/
public class FragmentedFramesExample {
- public static void main( String[] args ) throws URISyntaxException , IOException , InterruptedException {
- // WebSocketImpl.DEBUG = true; // will give extra output
- WebSocketClient websocket = new ExampleClient( new URI( "ws://localhost:8887" ));
- if( !websocket.connectBlocking() ) {
- System.err.println( "Could not connect to the server." );
- return;
- }
+ public static void main(String[] args)
+ throws URISyntaxException, IOException, InterruptedException {
+ // WebSocketImpl.DEBUG = true; // will give extra output
+
+ WebSocketClient websocket = new ExampleClient(new URI("ws://localhost:8887"));
+ if (!websocket.connectBlocking()) {
+ System.err.println("Could not connect to the server.");
+ return;
+ }
- System.out.println( "This example shows how to send fragmented(continuous) messages." );
+ System.out.println("This example shows how to send fragmented(continuous) messages.");
- BufferedReader stdin = new BufferedReader( new InputStreamReader( System.in ) );
- while ( websocket.isOpen() ) {
- System.out.println( "Please type in a loooooong line(which then will be send in 2 byte fragments):" );
- String longline = stdin.readLine();
- ByteBuffer longelinebuffer = ByteBuffer.wrap( longline.getBytes() );
- longelinebuffer.rewind();
+ BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
+ while (websocket.isOpen()) {
+ System.out
+ .println("Please type in a loooooong line(which then will be send in 2 byte fragments):");
+ String longline = stdin.readLine();
+ ByteBuffer longelinebuffer = ByteBuffer.wrap(longline.getBytes());
+ longelinebuffer.rewind();
- for( int position = 2 ; ; position += 2 ) {
- if( position < longelinebuffer.capacity() ) {
- longelinebuffer.limit( position );
- websocket.sendFragmentedFrame( Opcode.TEXT, longelinebuffer, false );// when sending binary data one should use Opcode.BINARY
- assert ( longelinebuffer.remaining() == 0 );
- // after calling sendFragmentedFrame one may reuse the buffer given to the method immediately
- } else {
- longelinebuffer.limit( longelinebuffer.capacity() );
- websocket.sendFragmentedFrame( Opcode.TEXT, longelinebuffer, true );// sending the last frame
- break;
- }
+ for (int position = 2; ; position += 2) {
+ if (position < longelinebuffer.capacity()) {
+ longelinebuffer.limit(position);
+ websocket.sendFragmentedFrame(Opcode.TEXT, longelinebuffer,
+ false);// when sending binary data one should use Opcode.BINARY
+ assert (longelinebuffer.remaining() == 0);
+ // after calling sendFragmentedFrame one may reuse the buffer given to the method immediately
+ } else {
+ longelinebuffer.limit(longelinebuffer.capacity());
+ websocket
+ .sendFragmentedFrame(Opcode.TEXT, longelinebuffer, true);// sending the last frame
+ break;
+ }
- }
- System.out.println( "You can not type in the next long message or press Ctr-C to exit." );
- }
- System.out.println( "FragmentedFramesExample terminated" );
- }
+ }
+ System.out.println("You can not type in the next long message or press Ctr-C to exit.");
+ }
+ System.out.println("FragmentedFramesExample terminated");
+ }
}
diff --git a/src/main/example/PerMessageDeflateExample.java b/src/main/example/PerMessageDeflateExample.java
new file mode 100644
index 000000000..49cd64eca
--- /dev/null
+++ b/src/main/example/PerMessageDeflateExample.java
@@ -0,0 +1,82 @@
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Collections;
+import org.java_websocket.WebSocket;
+import org.java_websocket.client.WebSocketClient;
+import org.java_websocket.drafts.Draft;
+import org.java_websocket.drafts.Draft_6455;
+import org.java_websocket.extensions.permessage_deflate.PerMessageDeflateExtension;
+import org.java_websocket.handshake.ClientHandshake;
+import org.java_websocket.handshake.ServerHandshake;
+import org.java_websocket.server.WebSocketServer;
+
+/**
+ * This class only serves the purpose of showing how to enable PerMessageDeflateExtension for both
+ * server and client sockets.
Extensions are required to be registered in
+ *
+ * @see Draft objects and both
+ * @see WebSocketClient and
+ * @see WebSocketServer accept a
+ * @see Draft object in their constructors. This example shows how to achieve it for both server and
+ * client sockets. Once the connection has been established, PerMessageDeflateExtension will be
+ * enabled and any messages (binary or text) will be compressed/decompressed automatically.
+ * Since no additional code is required when sending or receiving messages, this example skips those
+ * parts.
+ */
+public class PerMessageDeflateExample {
+
+ private static final Draft perMessageDeflateDraft = new Draft_6455(
+ new PerMessageDeflateExtension());
+ private static final int PORT = 8887;
+
+ private static class DeflateClient extends WebSocketClient {
+
+ public DeflateClient() throws URISyntaxException {
+ super(new URI("ws://localhost:" + PORT), perMessageDeflateDraft);
+ }
+
+ @Override
+ public void onOpen(ServerHandshake handshakedata) {
+ }
+
+ @Override
+ public void onMessage(String message) {
+ }
+
+ @Override
+ public void onClose(int code, String reason, boolean remote) {
+ }
+
+ @Override
+ public void onError(Exception ex) {
+ }
+ }
+
+ private static class DeflateServer extends WebSocketServer {
+
+ public DeflateServer() {
+ super(new InetSocketAddress(PORT), Collections.singletonList(perMessageDeflateDraft));
+ }
+
+ @Override
+ public void onOpen(WebSocket conn, ClientHandshake handshake) {
+ }
+
+ @Override
+ public void onClose(WebSocket conn, int code, String reason, boolean remote) {
+ }
+
+ @Override
+ public void onMessage(WebSocket conn, String message) {
+ }
+
+ @Override
+ public void onError(WebSocket conn, Exception ex) {
+ }
+
+ @Override
+ public void onStart() {
+ }
+ }
+}
diff --git a/src/main/example/ProxyClientExample.java b/src/main/example/ProxyClientExample.java
index 02c10d082..4241359ed 100644
--- a/src/main/example/ProxyClientExample.java
+++ b/src/main/example/ProxyClientExample.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -29,9 +29,10 @@
import java.net.URISyntaxException;
public class ProxyClientExample {
- public static void main( String[] args ) throws URISyntaxException {
- ExampleClient c = new ExampleClient( new URI( "ws://echo.websocket.org" ) );
- c.setProxy( new Proxy( Proxy.Type.HTTP, new InetSocketAddress( "proxyaddress", 80 ) ) );
- c.connect();
- }
+
+ public static void main(String[] args) throws URISyntaxException {
+ ExampleClient c = new ExampleClient(new URI("ws://echo.websocket.org"));
+ c.setProxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("proxyaddress", 80)));
+ c.connect();
+ }
}
diff --git a/src/main/example/ReconnectClientExample.java b/src/main/example/ReconnectClientExample.java
index c7181e838..efe958d54 100644
--- a/src/main/example/ReconnectClientExample.java
+++ b/src/main/example/ReconnectClientExample.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -23,8 +23,6 @@
* OTHER DEALINGS IN THE SOFTWARE.
*/
-import org.java_websocket.WebSocketImpl;
-
import java.net.URI;
import java.net.URISyntaxException;
@@ -32,24 +30,24 @@
* Simple example to reconnect blocking and non-blocking.
*/
public class ReconnectClientExample {
- public static void main( String[] args ) throws URISyntaxException, InterruptedException {
- WebSocketImpl.DEBUG = true;
- ExampleClient c = new ExampleClient( new URI( "ws://localhost:8887" ) );
- //Connect to a server normally
- c.connectBlocking();
- c.send( "hi" );
- Thread.sleep( 10 );
- c.closeBlocking();
- //Disconnect manually and reconnect blocking
- c.reconnectBlocking();
- c.send( "it's" );
- Thread.sleep( 10000 );
- c.closeBlocking();
- //Disconnect manually and reconnect
- c.reconnect();
- Thread.sleep( 100 );
- c.send( "me" );
- Thread.sleep( 100 );
- c.closeBlocking();
- }
+
+ public static void main(String[] args) throws URISyntaxException, InterruptedException {
+ ExampleClient c = new ExampleClient(new URI("ws://localhost:8887"));
+ //Connect to a server normally
+ c.connectBlocking();
+ c.send("hi");
+ Thread.sleep(10);
+ c.closeBlocking();
+ //Disconnect manually and reconnect blocking
+ c.reconnectBlocking();
+ c.send("it's");
+ Thread.sleep(10000);
+ c.closeBlocking();
+ //Disconnect manually and reconnect
+ c.reconnect();
+ Thread.sleep(100);
+ c.send("me");
+ Thread.sleep(100);
+ c.closeBlocking();
+ }
}
diff --git a/src/main/example/SSLClientExample.java b/src/main/example/SSLClientExample.java
index 693c71bbe..52790caff 100644
--- a/src/main/example/SSLClientExample.java
+++ b/src/main/example/SSLClientExample.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -28,97 +28,96 @@
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.net.URI;
+import java.nio.file.Paths;
import java.security.KeyStore;
-
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;
-
-import org.java_websocket.WebSocketImpl;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
class WebSocketChatClient extends WebSocketClient {
- public WebSocketChatClient( URI serverUri ) {
- super( serverUri );
- }
+ public WebSocketChatClient(URI serverUri) {
+ super(serverUri);
+ }
- @Override
- public void onOpen( ServerHandshake handshakedata ) {
- System.out.println( "Connected" );
+ @Override
+ public void onOpen(ServerHandshake handshakedata) {
+ System.out.println("Connected");
- }
+ }
- @Override
- public void onMessage( String message ) {
- System.out.println( "got: " + message );
+ @Override
+ public void onMessage(String message) {
+ System.out.println("got: " + message);
- }
+ }
- @Override
- public void onClose( int code, String reason, boolean remote ) {
- System.out.println( "Disconnected" );
- System.exit( 0 );
+ @Override
+ public void onClose(int code, String reason, boolean remote) {
+ System.out.println("Disconnected");
- }
+ }
- @Override
- public void onError( Exception ex ) {
- ex.printStackTrace();
+ @Override
+ public void onError(Exception ex) {
+ ex.printStackTrace();
- }
+ }
}
public class SSLClientExample {
- /*
- * Keystore with certificate created like so (in JKS format):
- *
- *keytool -genkey -validity 3650 -keystore "keystore.jks" -storepass "storepassword" -keypass "keypassword" -alias "default" -dname "CN=127.0.0.1, OU=MyOrgUnit, O=MyOrg, L=MyCity, S=MyRegion, C=MyCountry"
- */
- public static void main( String[] args ) throws Exception {
- WebSocketImpl.DEBUG = true;
-
- WebSocketChatClient chatclient = new WebSocketChatClient( new URI( "wss://localhost:8887" ) );
-
- // load up the key store
- String STORETYPE = "JKS";
- String KEYSTORE = "keystore.jks";
- String STOREPASSWORD = "storepassword";
- String KEYPASSWORD = "keypassword";
-
- KeyStore ks = KeyStore.getInstance( STORETYPE );
- File kf = new File( KEYSTORE );
- ks.load( new FileInputStream( kf ), STOREPASSWORD.toCharArray() );
-
- KeyManagerFactory kmf = KeyManagerFactory.getInstance( "SunX509" );
- kmf.init( ks, KEYPASSWORD.toCharArray() );
- TrustManagerFactory tmf = TrustManagerFactory.getInstance( "SunX509" );
- tmf.init( ks );
-
- SSLContext sslContext = null;
- sslContext = SSLContext.getInstance( "TLS" );
- sslContext.init( kmf.getKeyManagers(), tmf.getTrustManagers(), null );
- // sslContext.init( null, null, null ); // will use java's default key and trust store which is sufficient unless you deal with self-signed certificates
-
- SSLSocketFactory factory = sslContext.getSocketFactory();// (SSLSocketFactory) SSLSocketFactory.getDefault();
-
- chatclient.setSocket( factory.createSocket() );
-
- chatclient.connectBlocking();
-
- BufferedReader reader = new BufferedReader( new InputStreamReader( System.in ) );
- while ( true ) {
- String line = reader.readLine();
- if( line.equals( "close" ) ) {
- chatclient.close();
- } else {
- chatclient.send( line );
- }
- }
-
- }
+ /*
+ * Keystore with certificate created like so (in JKS format):
+ *
+ *keytool -genkey -keyalg RSA -validity 3650 -keystore "keystore.jks" -storepass "storepassword" -keypass "keypassword" -alias "default" -dname "CN=127.0.0.1, OU=MyOrgUnit, O=MyOrg, L=MyCity, S=MyRegion, C=MyCountry"
+ */
+ public static void main(String[] args) throws Exception {
+ WebSocketChatClient chatclient = new WebSocketChatClient(new URI("wss://localhost:8887"));
+
+ // load up the key store
+ String STORETYPE = "JKS";
+ String KEYSTORE = Paths.get("src", "test", "java", "org", "java_websocket", "keystore.jks")
+ .toString();
+ String STOREPASSWORD = "storepassword";
+ String KEYPASSWORD = "keypassword";
+
+ KeyStore ks = KeyStore.getInstance(STORETYPE);
+ File kf = new File(KEYSTORE);
+ ks.load(new FileInputStream(kf), STOREPASSWORD.toCharArray());
+
+ KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
+ kmf.init(ks, KEYPASSWORD.toCharArray());
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
+ tmf.init(ks);
+
+ SSLContext sslContext = null;
+ sslContext = SSLContext.getInstance("TLS");
+ sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
+ // sslContext.init( null, null, null ); // will use java's default key and trust store which is sufficient unless you deal with self-signed certificates
+
+ SSLSocketFactory factory = sslContext
+ .getSocketFactory();// (SSLSocketFactory) SSLSocketFactory.getDefault();
+
+ chatclient.setSocketFactory(factory);
+
+ chatclient.connectBlocking();
+
+ BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
+ while (true) {
+ String line = reader.readLine();
+ if (line.equals("close")) {
+ chatclient.closeBlocking();
+ } else if (line.equals("open")) {
+ chatclient.reconnect();
+ } else {
+ chatclient.send(line);
+ }
+ }
+
+ }
}
diff --git a/src/main/example/SSLServerCustomWebsocketFactoryExample.java b/src/main/example/SSLServerCustomWebsocketFactoryExample.java
index e9d825693..e9c5d7c1e 100644
--- a/src/main/example/SSLServerCustomWebsocketFactoryExample.java
+++ b/src/main/example/SSLServerCustomWebsocketFactoryExample.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -23,70 +23,70 @@
* OTHER DEALINGS IN THE SOFTWARE.
*/
-import org.java_websocket.WebSocketImpl;
-import org.java_websocket.server.CustomSSLWebSocketServerFactory;
-
-import javax.net.ssl.KeyManagerFactory;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLEngine;
-import javax.net.ssl.TrustManagerFactory;
import java.io.File;
import java.io.FileInputStream;
+import java.nio.file.Paths;
import java.security.KeyStore;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.TrustManagerFactory;
+import org.java_websocket.server.CustomSSLWebSocketServerFactory;
/**
* Example for using the CustomSSLWebSocketServerFactory to allow just specific cipher suites
*/
public class SSLServerCustomWebsocketFactoryExample {
- /*
- * Keystore with certificate created like so (in JKS format):
- *
- *keytool -genkey -validity 3650 -keystore "keystore.jks" -storepass "storepassword" -keypass "keypassword" -alias "default" -dname "CN=127.0.0.1, OU=MyOrgUnit, O=MyOrg, L=MyCity, S=MyRegion, C=MyCountry"
- */
- public static void main(String[] args) throws Exception {
- WebSocketImpl.DEBUG = true;
-
- ChatServer chatserver = new ChatServer(8887); // Firefox does allow multible ssl connection only via port 443 //tested on FF16
+ /*
+ * Keystore with certificate created like so (in JKS format):
+ *
+ *keytool -genkey -validity 3650 -keystore "keystore.jks" -storepass "storepassword" -keypass "keypassword" -alias "default" -dname "CN=127.0.0.1, OU=MyOrgUnit, O=MyOrg, L=MyCity, S=MyRegion, C=MyCountry"
+ */
+ public static void main(String[] args) throws Exception {
+ ChatServer chatserver = new ChatServer(
+ 8887); // Firefox does allow multible ssl connection only via port 443 //tested on FF16
- // load up the key store
- String STORETYPE = "JKS";
- String KEYSTORE = "keystore.jks";
- String STOREPASSWORD = "storepassword";
- String KEYPASSWORD = "keypassword";
+ // load up the key store
+ String STORETYPE = "JKS";
+ String KEYSTORE = Paths.get("src", "test", "java", "org", "java_websocket", "keystore.jks")
+ .toString();
+ String STOREPASSWORD = "storepassword";
+ String KEYPASSWORD = "keypassword";
- KeyStore ks = KeyStore.getInstance(STORETYPE);
- File kf = new File(KEYSTORE);
- ks.load(new FileInputStream(kf), STOREPASSWORD.toCharArray());
+ KeyStore ks = KeyStore.getInstance(STORETYPE);
+ File kf = new File(KEYSTORE);
+ ks.load(new FileInputStream(kf), STOREPASSWORD.toCharArray());
- KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
- kmf.init(ks, KEYPASSWORD.toCharArray());
- TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
- tmf.init(ks);
+ KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
+ kmf.init(ks, KEYPASSWORD.toCharArray());
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
+ tmf.init(ks);
- SSLContext sslContext = SSLContext.getInstance("TLS");
- sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
+ SSLContext sslContext = SSLContext.getInstance("TLS");
+ sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
- //Lets remove some ciphers and protocols
- SSLEngine engine = sslContext.createSSLEngine();
- List ciphers = new ArrayList( Arrays.asList(engine.getEnabledCipherSuites()));
- ciphers.remove("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256");
- List protocols = new ArrayList( Arrays.asList(engine.getEnabledProtocols()));
- protocols.remove("SSLv3");
- CustomSSLWebSocketServerFactory factory = new CustomSSLWebSocketServerFactory(sslContext, protocols.toArray(new String[]{}), ciphers.toArray(new String[]{}));
+ //Lets remove some ciphers and protocols
+ SSLEngine engine = sslContext.createSSLEngine();
+ List ciphers = new ArrayList(Arrays.asList(engine.getEnabledCipherSuites()));
+ ciphers.remove("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256");
+ List protocols = new ArrayList(Arrays.asList(engine.getEnabledProtocols()));
+ protocols.remove("SSLv3");
+ CustomSSLWebSocketServerFactory factory = new CustomSSLWebSocketServerFactory(sslContext,
+ protocols.toArray(new String[]{}), ciphers.toArray(new String[]{}));
- // Different example just using specific ciphers and protocols
+ // Different example just using specific ciphers and protocols
/*
String[] enabledProtocols = {"TLSv1.2"};
String[] enabledCipherSuites = {"TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA"};
CustomSSLWebSocketServerFactory factory = new CustomSSLWebSocketServerFactory(sslContext, enabledProtocols,enabledCipherSuites);
*/
- chatserver.setWebSocketFactory(factory);
+ chatserver.setWebSocketFactory(factory);
- chatserver.start();
+ chatserver.start();
- }
+ }
}
diff --git a/src/main/example/SSLServerExample.java b/src/main/example/SSLServerExample.java
index 1e58d4ae4..ca599f703 100644
--- a/src/main/example/SSLServerExample.java
+++ b/src/main/example/SSLServerExample.java
@@ -1,7 +1,7 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -27,49 +27,47 @@
import java.io.File;
import java.io.FileInputStream;
+import java.nio.file.Paths;
import java.security.KeyStore;
-
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
-
-import org.java_websocket.WebSocketImpl;
import org.java_websocket.server.DefaultSSLWebSocketServerFactory;
public class SSLServerExample {
- /*
- * Keystore with certificate created like so (in JKS format):
- *
- *keytool -genkey -validity 3650 -keystore "keystore.jks" -storepass "storepassword" -keypass "keypassword" -alias "default" -dname "CN=127.0.0.1, OU=MyOrgUnit, O=MyOrg, L=MyCity, S=MyRegion, C=MyCountry"
- */
- public static void main( String[] args ) throws Exception {
- WebSocketImpl.DEBUG = true;
-
- ChatServer chatserver = new ChatServer( 8887 ); // Firefox does allow multible ssl connection only via port 443 //tested on FF16
+ /*
+ * Keystore with certificate created like so (in JKS format):
+ *
+ *keytool -genkey -keyalg RSA -validity 3650 -keystore "keystore.jks" -storepass "storepassword" -keypass "keypassword" -alias "default" -dname "CN=127.0.0.1, OU=MyOrgUnit, O=MyOrg, L=MyCity, S=MyRegion, C=MyCountry"
+ */
+ public static void main(String[] args) throws Exception {
+ ChatServer chatserver = new ChatServer(
+ 8887); // Firefox does allow multible ssl connection only via port 443 //tested on FF16
- // load up the key store
- String STORETYPE = "JKS";
- String KEYSTORE = "keystore.jks";
- String STOREPASSWORD = "storepassword";
- String KEYPASSWORD = "keypassword";
+ // load up the key store
+ String STORETYPE = "JKS";
+ String KEYSTORE = Paths.get("src", "test", "java", "org", "java_websocket", "keystore.jks")
+ .toString();
+ String STOREPASSWORD = "storepassword";
+ String KEYPASSWORD = "keypassword";
- KeyStore ks = KeyStore.getInstance( STORETYPE );
- File kf = new File( KEYSTORE );
- ks.load( new FileInputStream( kf ), STOREPASSWORD.toCharArray() );
+ KeyStore ks = KeyStore.getInstance(STORETYPE);
+ File kf = new File(KEYSTORE);
+ ks.load(new FileInputStream(kf), STOREPASSWORD.toCharArray());
- KeyManagerFactory kmf = KeyManagerFactory.getInstance( "SunX509" );
- kmf.init( ks, KEYPASSWORD.toCharArray() );
- TrustManagerFactory tmf = TrustManagerFactory.getInstance( "SunX509" );
- tmf.init( ks );
+ KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
+ kmf.init(ks, KEYPASSWORD.toCharArray());
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
+ tmf.init(ks);
- SSLContext sslContext = null;
- sslContext = SSLContext.getInstance( "TLS" );
- sslContext.init( kmf.getKeyManagers(), tmf.getTrustManagers(), null );
+ SSLContext sslContext = null;
+ sslContext = SSLContext.getInstance("TLS");
+ sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
- chatserver.setWebSocketFactory( new DefaultSSLWebSocketServerFactory( sslContext ) );
+ chatserver.setWebSocketFactory(new DefaultSSLWebSocketServerFactory(sslContext));
- chatserver.start();
+ chatserver.start();
- }
+ }
}
diff --git a/src/main/example/SSLServerLetsEncryptExample.java b/src/main/example/SSLServerLetsEncryptExample.java
index 731bb3891..95308aa99 100644
--- a/src/main/example/SSLServerLetsEncryptExample.java
+++ b/src/main/example/SSLServerLetsEncryptExample.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -23,13 +23,6 @@
* OTHER DEALINGS IN THE SOFTWARE.
*/
-import org.java_websocket.WebSocketImpl;
-import org.java_websocket.server.DefaultSSLWebSocketServerFactory;
-
-import javax.net.ssl.KeyManager;
-import javax.net.ssl.KeyManagerFactory;
-import javax.net.ssl.SSLContext;
-import javax.xml.bind.DatatypeConverter;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
@@ -44,90 +37,97 @@
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import org.java_websocket.server.DefaultSSLWebSocketServerFactory;
/**
- * SSL Example using the LetsEncrypt certificate
- * See https://github.com/TooTallNate/Java-WebSocket/wiki/Getting-a-SSLContext-from-different-sources#getting-a-sslcontext-using-a-lets-encrypt-certificate
+ * SSL Example using the LetsEncrypt certificate See https://github.com/TooTallNate/Java-WebSocket/wiki/Getting-a-SSLContext-from-different-sources#getting-a-sslcontext-using-a-lets-encrypt-certificate
*/
public class SSLServerLetsEncryptExample {
- public static void main( String[] args ) throws Exception {
- WebSocketImpl.DEBUG = true;
-
- ChatServer chatserver = new ChatServer( 8887 );
-
- SSLContext context = getContext();
- if( context != null ) {
- chatserver.setWebSocketFactory( new DefaultSSLWebSocketServerFactory( getContext() ) );
- }
- chatserver.setConnectionLostTimeout( 30 );
- chatserver.start();
-
- }
-
- private static SSLContext getContext() {
- SSLContext context;
- String password = "CHANGEIT";
- String pathname = "pem";
- try {
- context = SSLContext.getInstance( "TLS" );
-
- byte[] certBytes = parseDERFromPEM( getBytes( new File( pathname + File.separator + "cert.pem" ) ), "-----BEGIN CERTIFICATE-----", "-----END CERTIFICATE-----" );
- byte[] keyBytes = parseDERFromPEM( getBytes( new File( pathname + File.separator + "privkey.pem" ) ), "-----BEGIN PRIVATE KEY-----", "-----END PRIVATE KEY-----" );
-
- X509Certificate cert = generateCertificateFromDER( certBytes );
- RSAPrivateKey key = generatePrivateKeyFromDER( keyBytes );
-
- KeyStore keystore = KeyStore.getInstance( "JKS" );
- keystore.load( null );
- keystore.setCertificateEntry( "cert-alias", cert );
- keystore.setKeyEntry( "key-alias", key, password.toCharArray(), new Certificate[]{ cert } );
-
- KeyManagerFactory kmf = KeyManagerFactory.getInstance( "SunX509" );
- kmf.init( keystore, password.toCharArray() );
-
- KeyManager[] km = kmf.getKeyManagers();
-
- context.init( km, null, null );
- } catch ( Exception e ) {
- context = null;
- }
- return context;
- }
-
- private static byte[] parseDERFromPEM( byte[] pem, String beginDelimiter, String endDelimiter ) {
- String data = new String( pem );
- String[] tokens = data.split( beginDelimiter );
- tokens = tokens[1].split( endDelimiter );
- return DatatypeConverter.parseBase64Binary( tokens[0] );
- }
-
- private static RSAPrivateKey generatePrivateKeyFromDER( byte[] keyBytes ) throws InvalidKeySpecException, NoSuchAlgorithmException {
- PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec( keyBytes );
-
- KeyFactory factory = KeyFactory.getInstance( "RSA" );
-
- return ( RSAPrivateKey ) factory.generatePrivate( spec );
- }
-
- private static X509Certificate generateCertificateFromDER( byte[] certBytes ) throws CertificateException {
- CertificateFactory factory = CertificateFactory.getInstance( "X.509" );
-
- return ( X509Certificate ) factory.generateCertificate( new ByteArrayInputStream( certBytes ) );
- }
-
- private static byte[] getBytes( File file ) {
- byte[] bytesArray = new byte[( int ) file.length()];
-
- FileInputStream fis = null;
- try {
- fis = new FileInputStream( file );
- fis.read( bytesArray ); //read file into bytes[]
- fis.close();
- } catch ( IOException e ) {
- e.printStackTrace();
- }
- return bytesArray;
- }
+ public static void main(String[] args) throws Exception {
+ ChatServer chatserver = new ChatServer(8887);
+
+ SSLContext context = getContext();
+ if (context != null) {
+ chatserver.setWebSocketFactory(new DefaultSSLWebSocketServerFactory(getContext()));
+ }
+ chatserver.setConnectionLostTimeout(30);
+ chatserver.start();
+
+ }
+
+ private static SSLContext getContext() {
+ SSLContext context;
+ String password = "CHANGEIT";
+ String pathname = "pem";
+ try {
+ context = SSLContext.getInstance("TLS");
+
+ byte[] certBytes = parseDERFromPEM(getBytes(new File(pathname + File.separator + "cert.pem")),
+ "-----BEGIN CERTIFICATE-----", "-----END CERTIFICATE-----");
+ byte[] keyBytes = parseDERFromPEM(
+ getBytes(new File(pathname + File.separator + "privkey.pem")),
+ "-----BEGIN PRIVATE KEY-----", "-----END PRIVATE KEY-----");
+
+ X509Certificate cert = generateCertificateFromDER(certBytes);
+ RSAPrivateKey key = generatePrivateKeyFromDER(keyBytes);
+
+ KeyStore keystore = KeyStore.getInstance("JKS");
+ keystore.load(null);
+ keystore.setCertificateEntry("cert-alias", cert);
+ keystore.setKeyEntry("key-alias", key, password.toCharArray(), new Certificate[]{cert});
+
+ KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
+ kmf.init(keystore, password.toCharArray());
+
+ KeyManager[] km = kmf.getKeyManagers();
+
+ context.init(km, null, null);
+ } catch (Exception e) {
+ context = null;
+ }
+ return context;
+ }
+
+ private static byte[] parseDERFromPEM(byte[] pem, String beginDelimiter, String endDelimiter) {
+ String data = new String(pem);
+ String[] tokens = data.split(beginDelimiter);
+ tokens = tokens[1].split(endDelimiter);
+ // return DatatypeConverter.parseBase64Binary(tokens[0]);
+ return null;
+ }
+
+ private static RSAPrivateKey generatePrivateKeyFromDER(byte[] keyBytes)
+ throws InvalidKeySpecException, NoSuchAlgorithmException {
+ PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
+
+ KeyFactory factory = KeyFactory.getInstance("RSA");
+
+ return (RSAPrivateKey) factory.generatePrivate(spec);
+ }
+
+ private static X509Certificate generateCertificateFromDER(byte[] certBytes)
+ throws CertificateException {
+ CertificateFactory factory = CertificateFactory.getInstance("X.509");
+
+ return (X509Certificate) factory.generateCertificate(new ByteArrayInputStream(certBytes));
+ }
+
+ private static byte[] getBytes(File file) {
+ byte[] bytesArray = new byte[(int) file.length()];
+
+ FileInputStream fis = null;
+ try {
+ fis = new FileInputStream(file);
+ fis.read(bytesArray); //read file into bytes[]
+ fis.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return bytesArray;
+ }
}
diff --git a/src/main/example/SecWebSocketProtocolClientExample.java b/src/main/example/SecWebSocketProtocolClientExample.java
index e5bb1b244..d9645f800 100644
--- a/src/main/example/SecWebSocketProtocolClientExample.java
+++ b/src/main/example/SecWebSocketProtocolClientExample.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -23,34 +23,33 @@
* OTHER DEALINGS IN THE SOFTWARE.
*/
-import org.java_websocket.WebSocketImpl;
-import org.java_websocket.drafts.Draft_6455;
-import org.java_websocket.extensions.IExtension;
-import org.java_websocket.protocols.IProtocol;
-import org.java_websocket.protocols.Protocol;
-
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
+import org.java_websocket.drafts.Draft_6455;
+import org.java_websocket.extensions.IExtension;
+import org.java_websocket.protocols.IProtocol;
+import org.java_websocket.protocols.Protocol;
/**
* This example demonstrates how to use a specific Sec-WebSocket-Protocol for your connection.
*/
public class SecWebSocketProtocolClientExample {
- public static void main( String[] args ) throws URISyntaxException {
- WebSocketImpl.DEBUG = true;
- // This draft only allows you to use the specific Sec-WebSocket-Protocol without a fallback.
- Draft_6455 draft_ocppOnly = new Draft_6455(Collections.emptyList(), Collections.singletonList(new Protocol("ocpp2.0")));
+ public static void main(String[] args) throws URISyntaxException {
+ // This draft only allows you to use the specific Sec-WebSocket-Protocol without a fallback.
+ Draft_6455 draft_ocppOnly = new Draft_6455(Collections.emptyList(),
+ Collections.singletonList(new Protocol("ocpp2.0")));
- // This draft allows the specific Sec-WebSocket-Protocol and also provides a fallback, if the other endpoint does not accept the specific Sec-WebSocket-Protocol
- ArrayList protocols = new ArrayList();
- protocols.add(new Protocol("ocpp2.0"));
- protocols.add(new Protocol(""));
- Draft_6455 draft_ocppAndFallBack = new Draft_6455(Collections.emptyList(), protocols);
+ // This draft allows the specific Sec-WebSocket-Protocol and also provides a fallback, if the other endpoint does not accept the specific Sec-WebSocket-Protocol
+ ArrayList protocols = new ArrayList();
+ protocols.add(new Protocol("ocpp2.0"));
+ protocols.add(new Protocol(""));
+ Draft_6455 draft_ocppAndFallBack = new Draft_6455(Collections.emptyList(),
+ protocols);
- ExampleClient c = new ExampleClient( new URI( "ws://echo.websocket.org" ), draft_ocppAndFallBack );
- c.connect();
- }
+ ExampleClient c = new ExampleClient(new URI("ws://echo.websocket.org"), draft_ocppAndFallBack);
+ c.connect();
+ }
}
diff --git a/src/main/example/SecWebSocketProtocolServerExample.java b/src/main/example/SecWebSocketProtocolServerExample.java
new file mode 100644
index 000000000..01ba44b02
--- /dev/null
+++ b/src/main/example/SecWebSocketProtocolServerExample.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2010-2020 Nathan Rajlich
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Collections;
+import org.java_websocket.drafts.Draft_6455;
+import org.java_websocket.extensions.IExtension;
+import org.java_websocket.protocols.IProtocol;
+import org.java_websocket.protocols.Protocol;
+
+/**
+ * This example demonstrates how to use a specific Sec-WebSocket-Protocol for your connection.
+ */
+public class SecWebSocketProtocolServerExample {
+
+ public static void main(String[] args) throws URISyntaxException {
+ // This draft only allows you to use the specific Sec-WebSocket-Protocol without a fallback.
+ Draft_6455 draft_ocppOnly = new Draft_6455(Collections.emptyList(),
+ Collections.singletonList(new Protocol("ocpp2.0")));
+
+ // This draft allows the specific Sec-WebSocket-Protocol and also provides a fallback, if the other endpoint does not accept the specific Sec-WebSocket-Protocol
+ ArrayList protocols = new ArrayList();
+ protocols.add(new Protocol("ocpp2.0"));
+ protocols.add(new Protocol(""));
+ Draft_6455 draft_ocppAndFallBack = new Draft_6455(Collections.emptyList(),
+ protocols);
+
+ ChatServer chatServer = new ChatServer(8887, draft_ocppOnly);
+ chatServer.start();
+ }
+}
diff --git a/src/main/example/ServerAdditionalHeaderExample.java b/src/main/example/ServerAdditionalHeaderExample.java
index 4b80e9e20..205c77f67 100644
--- a/src/main/example/ServerAdditionalHeaderExample.java
+++ b/src/main/example/ServerAdditionalHeaderExample.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -23,59 +23,57 @@
* OTHER DEALINGS IN THE SOFTWARE.
*/
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.InetSocketAddress;
import org.java_websocket.WebSocket;
-import org.java_websocket.WebSocketImpl;
import org.java_websocket.drafts.Draft;
import org.java_websocket.exceptions.InvalidDataException;
-import org.java_websocket.framing.CloseFrame;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.handshake.ServerHandshakeBuilder;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.net.InetSocketAddress;
-
/**
* This example shows how to add additional headers to your server handshake response
- *
+ *
* For this you have to override onWebsocketHandshakeReceivedAsServer in your WebSocketServer class
- *
+ *
* We are simple adding the additional header "Access-Control-Allow-Origin" to our server response
*/
public class ServerAdditionalHeaderExample extends ChatServer {
- public ServerAdditionalHeaderExample( int port ) {
- super( new InetSocketAddress( port ));
- }
+ public ServerAdditionalHeaderExample(int port) {
+ super(new InetSocketAddress(port));
+ }
- @Override
- public ServerHandshakeBuilder onWebsocketHandshakeReceivedAsServer( WebSocket conn, Draft draft, ClientHandshake request) throws InvalidDataException {
- ServerHandshakeBuilder builder = super.onWebsocketHandshakeReceivedAsServer( conn, draft, request );
- builder.put( "Access-Control-Allow-Origin" , "*");
- return builder;
- }
+ @Override
+ public ServerHandshakeBuilder onWebsocketHandshakeReceivedAsServer(WebSocket conn, Draft draft,
+ ClientHandshake request) throws InvalidDataException {
+ ServerHandshakeBuilder builder = super
+ .onWebsocketHandshakeReceivedAsServer(conn, draft, request);
+ builder.put("Access-Control-Allow-Origin", "*");
+ return builder;
+ }
- public static void main( String[] args ) throws InterruptedException , IOException {
- WebSocketImpl.DEBUG = true;
- int port = 8887; // 843 flash policy port
- try {
- port = Integer.parseInt( args[ 0 ] );
- } catch ( Exception ex ) {
- }
- ServerAdditionalHeaderExample s = new ServerAdditionalHeaderExample( port );
- s.start();
- System.out.println( "Server started on port: " + s.getPort() );
+ public static void main(String[] args) throws InterruptedException, IOException {
+ int port = 8887; // 843 flash policy port
+ try {
+ port = Integer.parseInt(args[0]);
+ } catch (Exception ex) {
+ }
+ ServerAdditionalHeaderExample s = new ServerAdditionalHeaderExample(port);
+ s.start();
+ System.out.println("Server started on port: " + s.getPort());
- BufferedReader sysin = new BufferedReader( new InputStreamReader( System.in ) );
- while ( true ) {
- String in = sysin.readLine();
- s.broadcast( in );
- if( in.equals( "exit" ) ) {
- s.stop(1000);
- break;
- }
- }
- }
+ BufferedReader sysin = new BufferedReader(new InputStreamReader(System.in));
+ while (true) {
+ String in = sysin.readLine();
+ s.broadcast(in);
+ if (in.equals("exit")) {
+ s.stop(1000);
+ break;
+ }
+ }
+ }
}
diff --git a/src/main/example/ServerRejectHandshakeExample.java b/src/main/example/ServerRejectHandshakeExample.java
index f2a3430f9..ee7081f15 100644
--- a/src/main/example/ServerRejectHandshakeExample.java
+++ b/src/main/example/ServerRejectHandshakeExample.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -23,74 +23,73 @@
* OTHER DEALINGS IN THE SOFTWARE.
*/
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.InetSocketAddress;
import org.java_websocket.WebSocket;
-import org.java_websocket.WebSocketImpl;
import org.java_websocket.drafts.Draft;
import org.java_websocket.exceptions.InvalidDataException;
import org.java_websocket.framing.CloseFrame;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.handshake.ServerHandshakeBuilder;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.net.InetSocketAddress;
-
/**
* This example shows how to reject a handshake as a server from a client.
- *
+ *
* For this you have to override onWebsocketHandshakeReceivedAsServer in your WebSocketServer class
*/
public class ServerRejectHandshakeExample extends ChatServer {
- public ServerRejectHandshakeExample( int port ) {
- super( new InetSocketAddress( port ));
- }
+ public ServerRejectHandshakeExample(int port) {
+ super(new InetSocketAddress(port));
+ }
- @Override
- public ServerHandshakeBuilder onWebsocketHandshakeReceivedAsServer( WebSocket conn, Draft draft, ClientHandshake request) throws InvalidDataException {
- ServerHandshakeBuilder builder = super.onWebsocketHandshakeReceivedAsServer( conn, draft, request );
- //In this example we don't allow any resource descriptor ( "ws://localhost:8887/?roomid=1 will be rejected but ws://localhost:8887 is fine)
- if (! request.getResourceDescriptor().equals( "/" )) {
- throw new InvalidDataException( CloseFrame.POLICY_VALIDATION, "Not accepted!");
- }
- //If there are no cookies set reject it as well.
- if (!request.hasFieldValue( "Cookie" )) {
- throw new InvalidDataException( CloseFrame.POLICY_VALIDATION, "Not accepted!");
- }
- //If the cookie does not contain a specific value
- if (!request.getFieldValue( "Cookie" ).equals( "username=nemo" )) {
- throw new InvalidDataException( CloseFrame.POLICY_VALIDATION, "Not accepted!");
- }
- //If there is a Origin Field, it has to be localhost:8887
- if (request.hasFieldValue( "Origin" )) {
- if (!request.getFieldValue( "Origin" ).equals( "localhost:8887" )) {
- throw new InvalidDataException( CloseFrame.POLICY_VALIDATION, "Not accepted!");
- }
- }
- return builder;
- }
+ @Override
+ public ServerHandshakeBuilder onWebsocketHandshakeReceivedAsServer(WebSocket conn, Draft draft,
+ ClientHandshake request) throws InvalidDataException {
+ ServerHandshakeBuilder builder = super
+ .onWebsocketHandshakeReceivedAsServer(conn, draft, request);
+ //In this example we don't allow any resource descriptor ( "ws://localhost:8887/?roomid=1 will be rejected but ws://localhost:8887 is fine)
+ if (!request.getResourceDescriptor().equals("/")) {
+ throw new InvalidDataException(CloseFrame.POLICY_VALIDATION, "Not accepted!");
+ }
+ //If there are no cookies set reject it as well.
+ if (!request.hasFieldValue("Cookie")) {
+ throw new InvalidDataException(CloseFrame.POLICY_VALIDATION, "Not accepted!");
+ }
+ //If the cookie does not contain a specific value
+ if (!request.getFieldValue("Cookie").equals("username=nemo")) {
+ throw new InvalidDataException(CloseFrame.POLICY_VALIDATION, "Not accepted!");
+ }
+ //If there is a Origin Field, it has to be localhost:8887
+ if (request.hasFieldValue("Origin")) {
+ if (!request.getFieldValue("Origin").equals("localhost:8887")) {
+ throw new InvalidDataException(CloseFrame.POLICY_VALIDATION, "Not accepted!");
+ }
+ }
+ return builder;
+ }
- public static void main( String[] args ) throws InterruptedException , IOException {
- WebSocketImpl.DEBUG = true;
- int port = 8887; // 843 flash policy port
- try {
- port = Integer.parseInt( args[ 0 ] );
- } catch ( Exception ex ) {
- }
- ServerRejectHandshakeExample s = new ServerRejectHandshakeExample( port );
- s.start();
- System.out.println( "Server started on port: " + s.getPort() );
+ public static void main(String[] args) throws InterruptedException, IOException {
+ int port = 8887; // 843 flash policy port
+ try {
+ port = Integer.parseInt(args[0]);
+ } catch (Exception ex) {
+ }
+ ServerRejectHandshakeExample s = new ServerRejectHandshakeExample(port);
+ s.start();
+ System.out.println("Server started on port: " + s.getPort());
- BufferedReader sysin = new BufferedReader( new InputStreamReader( System.in ) );
- while ( true ) {
- String in = sysin.readLine();
- s.broadcast( in );
- if( in.equals( "exit" ) ) {
- s.stop(1000);
- break;
- }
- }
- }
+ BufferedReader sysin = new BufferedReader(new InputStreamReader(System.in));
+ while (true) {
+ String in = sysin.readLine();
+ s.broadcast(in);
+ if (in.equals("exit")) {
+ s.stop(1000);
+ break;
+ }
+ }
+ }
}
diff --git a/src/main/example/ServerStressTest.java b/src/main/example/ServerStressTest.java
index f748208f7..bb973b63c 100644
--- a/src/main/example/ServerStressTest.java
+++ b/src/main/example/ServerStressTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -29,13 +29,11 @@
import java.awt.event.ActionListener;
import java.net.URI;
import java.net.URISyntaxException;
-import java.nio.channels.NotYetConnectedException;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
-
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
@@ -45,198 +43,206 @@
import javax.swing.JTextField;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
-
import org.java_websocket.client.WebSocketClient;
+import org.java_websocket.exceptions.WebsocketNotConnectedException;
public class ServerStressTest extends JFrame {
- private JSlider clients;
- private JSlider interval;
- private JSlider joinrate;
- private JButton start, stop, reset;
- private JLabel joinratelabel = new JLabel();
- private JLabel clientslabel = new JLabel();
- private JLabel intervallabel = new JLabel();
- private JTextField uriinput = new JTextField( "ws://localhost:8887" );
- private JTextArea text = new JTextArea( "payload" );
- private Timer timer = new Timer( true );
- private Thread adjustthread;
-
- private int notyetconnected = 0;
-
- public ServerStressTest() {
- setTitle( "ServerStressTest" );
- setDefaultCloseOperation( EXIT_ON_CLOSE );
- start = new JButton( "Start" );
- start.addActionListener( new ActionListener() {
-
- @Override
- public void actionPerformed( ActionEvent e ) {
- start.setEnabled( false );
- stop.setEnabled( true );
- reset.setEnabled( false );
- interval.setEnabled( false );
- clients.setEnabled( false );
-
- stopAdjust();
- adjustthread = new Thread( new Runnable() {
- @Override
- public void run() {
- try {
- adjust();
- } catch ( InterruptedException e ) {
- System.out.println( "adjust chanced" );
- }
- }
- } );
- adjustthread.start();
-
- }
- } );
- stop = new JButton( "Stop" );
- stop.setEnabled( false );
- stop.addActionListener( new ActionListener() {
-
- @Override
- public void actionPerformed( ActionEvent e ) {
- timer.cancel();
- stopAdjust();
- start.setEnabled( true );
- stop.setEnabled( false );
- reset.setEnabled( true );
- joinrate.setEnabled( true );
- interval.setEnabled( true );
- clients.setEnabled( true );
- }
- } );
- reset = new JButton( "reset" );
- reset.setEnabled( true );
- reset.addActionListener( new ActionListener() {
-
- @Override
- public void actionPerformed( ActionEvent e ) {
- while ( !websockets.isEmpty() )
- websockets.remove( 0 ).close();
-
- }
- } );
- joinrate = new JSlider( 0, 5000 );
- joinrate.addChangeListener( new ChangeListener() {
- @Override
- public void stateChanged( ChangeEvent e ) {
- joinratelabel.setText( "Joinrate: " + joinrate.getValue() + " ms " );
- }
- } );
- clients = new JSlider( 0, 10000 );
- clients.addChangeListener( new ChangeListener() {
-
- @Override
- public void stateChanged( ChangeEvent e ) {
- clientslabel.setText( "Clients: " + clients.getValue() );
-
- }
- } );
- interval = new JSlider( 0, 5000 );
- interval.addChangeListener( new ChangeListener() {
-
- @Override
- public void stateChanged( ChangeEvent e ) {
- intervallabel.setText( "Interval: " + interval.getValue() + " ms " );
-
- }
- } );
-
- setSize( 300, 400 );
- setLayout( new GridLayout( 10, 1, 10, 10 ) );
- add( new JLabel( "URI" ) );
- add( uriinput );
- add( joinratelabel );
- add( joinrate );
- add( clientslabel );
- add( clients );
- add( intervallabel );
- add( interval );
- JPanel south = new JPanel( new FlowLayout( FlowLayout.CENTER ) );
- add( text );
- add( south );
-
- south.add( start );
- south.add( stop );
- south.add( reset );
-
- joinrate.setValue( 200 );
- interval.setValue( 1000 );
- clients.setValue( 1 );
-
- }
-
- List websockets = Collections.synchronizedList( new LinkedList() );
- URI uri;
- public void adjust() throws InterruptedException {
- System.out.println( "Adjust" );
- try {
- uri = new URI( uriinput.getText() );
- } catch ( URISyntaxException e ) {
- e.printStackTrace();
- }
- int totalclients = clients.getValue();
- while ( websockets.size() < totalclients ) {
- WebSocketClient cl = new ExampleClient( uri ) {
- @Override
- public void onClose( int code, String reason, boolean remote ) {
- System.out.println( "Closed duo " + code + " " + reason );
- clients.setValue( websockets.size() );
- websockets.remove( this );
- }
- };
-
- cl.connect();
- clients.setValue( websockets.size() );
- websockets.add( cl );
- Thread.sleep( joinrate.getValue() );
- }
- while ( websockets.size() > clients.getValue() ) {
- websockets.remove( 0 ).close();
- }
- timer = new Timer( true );
- timer.scheduleAtFixedRate( new TimerTask() {
- @Override
- public void run() {
- send();
- }
- }, 0, interval.getValue() );
-
- }
-
- public void stopAdjust() {
- if( adjustthread != null ) {
- adjustthread.interrupt();
- try {
- adjustthread.join();
- } catch ( InterruptedException e ) {
- e.printStackTrace();
- }
- }
- }
- public void send() {
- notyetconnected = 0;
- String payload = text.getText();
- long time1 = System.currentTimeMillis();
- synchronized ( websockets ) {
- for( WebSocketClient cl : websockets ) {
- try {
- cl.send( payload );
- } catch ( NotYetConnectedException e ) {
- notyetconnected++;
- }
- }
- }
- System.out.println( websockets.size() + "/" + notyetconnected + " clients sent \"" + payload + "\"" + ( System.currentTimeMillis() - time1 ) );
- }
- /**
- * @param args
- */
- public static void main( String[] args ) {
- new ServerStressTest().setVisible( true );
- }
+
+ private JSlider clients;
+ private JSlider interval;
+ private JSlider joinrate;
+ private JButton start, stop, reset;
+ private JLabel joinratelabel = new JLabel();
+ private JLabel clientslabel = new JLabel();
+ private JLabel intervallabel = new JLabel();
+ private JTextField uriinput = new JTextField("ws://localhost:8887");
+ private JTextArea text = new JTextArea("payload");
+ private Timer timer = new Timer(true);
+ private Thread adjustthread;
+
+ private int notyetconnected = 0;
+
+ public ServerStressTest() {
+ setTitle("ServerStressTest");
+ setDefaultCloseOperation(EXIT_ON_CLOSE);
+ start = new JButton("Start");
+ start.addActionListener(new ActionListener() {
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ start.setEnabled(false);
+ stop.setEnabled(true);
+ reset.setEnabled(false);
+ interval.setEnabled(false);
+ clients.setEnabled(false);
+
+ stopAdjust();
+ adjustthread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ adjust();
+ } catch (InterruptedException e) {
+ System.out.println("adjust chanced");
+ }
+ }
+ });
+ adjustthread.start();
+
+ }
+ });
+ stop = new JButton("Stop");
+ stop.setEnabled(false);
+ stop.addActionListener(new ActionListener() {
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ timer.cancel();
+ stopAdjust();
+ start.setEnabled(true);
+ stop.setEnabled(false);
+ reset.setEnabled(true);
+ joinrate.setEnabled(true);
+ interval.setEnabled(true);
+ clients.setEnabled(true);
+ }
+ });
+ reset = new JButton("reset");
+ reset.setEnabled(true);
+ reset.addActionListener(new ActionListener() {
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ while (!websockets.isEmpty()) {
+ websockets.remove(0).close();
+ }
+
+ }
+ });
+ joinrate = new JSlider(0, 5000);
+ joinrate.addChangeListener(new ChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ joinratelabel.setText("Joinrate: " + joinrate.getValue() + " ms ");
+ }
+ });
+ clients = new JSlider(0, 10000);
+ clients.addChangeListener(new ChangeListener() {
+
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ clientslabel.setText("Clients: " + clients.getValue());
+
+ }
+ });
+ interval = new JSlider(0, 5000);
+ interval.addChangeListener(new ChangeListener() {
+
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ intervallabel.setText("Interval: " + interval.getValue() + " ms ");
+
+ }
+ });
+
+ setSize(300, 400);
+ setLayout(new GridLayout(10, 1, 10, 10));
+ add(new JLabel("URI"));
+ add(uriinput);
+ add(joinratelabel);
+ add(joinrate);
+ add(clientslabel);
+ add(clients);
+ add(intervallabel);
+ add(interval);
+ JPanel south = new JPanel(new FlowLayout(FlowLayout.CENTER));
+ add(text);
+ add(south);
+
+ south.add(start);
+ south.add(stop);
+ south.add(reset);
+
+ joinrate.setValue(200);
+ interval.setValue(1000);
+ clients.setValue(1);
+
+ }
+
+ List websockets = Collections
+ .synchronizedList(new LinkedList());
+ URI uri;
+
+ public void adjust() throws InterruptedException {
+ System.out.println("Adjust");
+ try {
+ uri = new URI(uriinput.getText());
+ } catch (URISyntaxException e) {
+ e.printStackTrace();
+ }
+ int totalclients = clients.getValue();
+ while (websockets.size() < totalclients) {
+ WebSocketClient cl = new ExampleClient(uri) {
+ @Override
+ public void onClose(int code, String reason, boolean remote) {
+ System.out.println("Closed duo " + code + " " + reason);
+ clients.setValue(websockets.size());
+ websockets.remove(this);
+ }
+ };
+
+ cl.connect();
+ clients.setValue(websockets.size());
+ websockets.add(cl);
+ Thread.sleep(joinrate.getValue());
+ }
+ while (websockets.size() > clients.getValue()) {
+ websockets.remove(0).close();
+ }
+ timer = new Timer(true);
+ timer.scheduleAtFixedRate(new TimerTask() {
+ @Override
+ public void run() {
+ send();
+ }
+ }, 0, interval.getValue());
+
+ }
+
+ public void stopAdjust() {
+ if (adjustthread != null) {
+ adjustthread.interrupt();
+ try {
+ adjustthread.join();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ public void send() {
+ notyetconnected = 0;
+ String payload = text.getText();
+ long time1 = System.currentTimeMillis();
+ synchronized (websockets) {
+ for (WebSocketClient cl : websockets) {
+ try {
+ cl.send(payload);
+ } catch (WebsocketNotConnectedException e) {
+ notyetconnected++;
+ }
+ }
+ }
+ System.out.println(
+ websockets.size() + "/" + notyetconnected + " clients sent \"" + payload + "\"" + (
+ System.currentTimeMillis() - time1));
+ }
+
+ /**
+ * @param args
+ */
+ public static void main(String[] args) {
+ new ServerStressTest().setVisible(true);
+ }
}
diff --git a/src/main/example/SocketActivation.java b/src/main/example/SocketActivation.java
new file mode 100644
index 000000000..86b0da63a
--- /dev/null
+++ b/src/main/example/SocketActivation.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2010-2020 Nathan Rajlich
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.ServerSocketChannel;
+import java.util.Collections;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.java_websocket.WebSocket;
+import org.java_websocket.drafts.Draft;
+import org.java_websocket.drafts.Draft_6455;
+import org.java_websocket.handshake.ClientHandshake;
+import org.java_websocket.server.WebSocketServer;
+
+/**
+ * This is a "smart" chat server which will exit when no more clients are left, in order to demonstrate socket activation
+ */
+public class SocketActivation extends WebSocketServer {
+
+ AtomicInteger clients = new AtomicInteger(0);
+
+ public SocketActivation(ServerSocketChannel chan) {
+ super(chan);
+ }
+
+ @Override
+ public void onOpen(WebSocket conn, ClientHandshake handshake) {
+ conn.send("Welcome to the server!"); //This method sends a message to the new client
+ broadcast("new connection: " + handshake.getResourceDescriptor()); //This method sends a message to all clients connected
+ if(clients.get() == 0) {
+ broadcast("You are the first client to join");
+ }
+ System.out.println(conn.getRemoteSocketAddress().getAddress().getHostAddress() + " entered the room!");
+ clients.incrementAndGet();
+ }
+
+ @Override
+ public void onClose(WebSocket conn, int code, String reason, boolean remote) {
+ broadcast(conn + " has left the room!");
+ System.out.println(conn + " has left the room!");
+ if(clients.decrementAndGet() <= 0) {
+ System.out.println("No more clients left, exiting");
+ System.exit(0);
+ }
+ }
+
+ @Override
+ public void onMessage(WebSocket conn, String message) {
+ broadcast(message);
+ System.out.println(conn + ": " + message);
+ }
+
+ @Override
+ public void onMessage(WebSocket conn, ByteBuffer message) {
+ broadcast(message.array());
+ System.out.println(conn + ": " + message);
+ }
+
+
+ public static void main(String[] args) throws InterruptedException, IOException {
+ if(System.inheritedChannel() == null) {
+ System.err.println("System.inheritedChannel() is null, make sure this program is started with file descriptor zero being a listening socket");
+ System.exit(1);
+ }
+ SocketActivation s = new SocketActivation((ServerSocketChannel)System.inheritedChannel());
+ s.start();
+ System.out.println(">>>> SocketActivation started on port: " + s.getPort() + " <<<<");
+ }
+
+ @Override
+ public void onError(WebSocket conn, Exception ex) {
+ ex.printStackTrace();
+ }
+
+ @Override
+ public void onStart() {
+ System.out.println("Server started!");
+ }
+
+}
diff --git a/src/main/example/TwoWaySSLServerExample.java b/src/main/example/TwoWaySSLServerExample.java
new file mode 100644
index 000000000..2e52f7aa2
--- /dev/null
+++ b/src/main/example/TwoWaySSLServerExample.java
@@ -0,0 +1,82 @@
+
+
+/*
+ * Copyright (c) 2010-2020 Nathan Rajlich
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.TrustManagerFactory;
+import org.java_websocket.server.SSLParametersWebSocketServerFactory;
+
+/**
+ * Copy of SSLServerExample except we use @link SSLEngineWebSocketServerFactory to customize
+ * clientMode/ClientAuth to force client to present a cert. Example of Two-way
+ * ssl/MutualAuthentication/ClientAuthentication
+ */
+public class TwoWaySSLServerExample {
+
+ /*
+ * Keystore with certificate created like so (in JKS format):
+ *
+ *keytool -genkey -keyalg RSA -validity 3650 -keystore "keystore.jks" -storepass "storepassword" -keypass "keypassword" -alias "default" -dname "CN=127.0.0.1, OU=MyOrgUnit, O=MyOrg, L=MyCity, S=MyRegion, C=MyCountry"
+ */
+ public static void main(String[] args) throws Exception {
+ ChatServer chatserver = new ChatServer(
+ 8887); // Firefox does allow multible ssl connection only via port 443 //tested on FF16
+
+ // load up the key store
+ String STORETYPE = "JKS";
+ String KEYSTORE = Paths.get("src", "test", "java", "org", "java_websocket", "keystore.jks")
+ .toString();
+ String STOREPASSWORD = "storepassword";
+ String KEYPASSWORD = "keypassword";
+
+ KeyStore ks = KeyStore.getInstance(STORETYPE);
+ File kf = new File(KEYSTORE);
+ ks.load(new FileInputStream(kf), STOREPASSWORD.toCharArray());
+
+ KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
+ kmf.init(ks, KEYPASSWORD.toCharArray());
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
+ tmf.init(ks);
+
+ SSLContext sslContext = SSLContext.getInstance("TLS");
+ sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
+
+ SSLParameters sslParameters = new SSLParameters();
+ // This is all we need
+ sslParameters.setNeedClientAuth(true);
+ chatserver
+ .setWebSocketFactory(new SSLParametersWebSocketServerFactory(sslContext, sslParameters));
+
+ chatserver.start();
+
+ }
+}
diff --git a/src/main/example/jws-activation.service b/src/main/example/jws-activation.service
new file mode 100644
index 000000000..0ae3d091a
--- /dev/null
+++ b/src/main/example/jws-activation.service
@@ -0,0 +1,17 @@
+[Unit]
+Description=Java-WebSocket systemd activation demo service
+After=network.target jws-activation.socket
+Requires=jws-activation.socket
+
+[Service]
+Type=simple
+# Place the command for running SocketActivation.java in file "$HOME"/jws_activation_command:
+ExecStart=/bin/sh %h/jws_activation_run
+TimeoutStopSec=5
+StandardError=journal
+StandardOutput=journal
+# This is very important - systemd will pass the socket as file descriptor zero, which is what Java expects
+StandardInput=socket
+
+[Install]
+WantedBy=default.target
diff --git a/src/main/example/jws-activation.socket b/src/main/example/jws-activation.socket
new file mode 100644
index 000000000..db769c3e1
--- /dev/null
+++ b/src/main/example/jws-activation.socket
@@ -0,0 +1,9 @@
+[Unit]
+Description=Java-WebSocket systemd activation demo socket
+PartOf=jws-activation.service
+
+[Socket]
+ListenStream=127.0.0.1:9999
+
+[Install]
+WantedBy=sockets.target
diff --git a/src/main/example/simplelogger.properties b/src/main/example/simplelogger.properties
new file mode 100644
index 000000000..794b8ad5a
--- /dev/null
+++ b/src/main/example/simplelogger.properties
@@ -0,0 +1,7 @@
+org.slf4j.simpleLogger.logFile=System.out
+org.slf4j.simpleLogger.defaultLogLevel=off
+org.slf4j.simpleLogger.showDateTime=true
+org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss.SSS
+org.slf4j.simpleLogger.showThreadName=false
+org.slf4j.simpleLogger.showLogName=false
+org.slf4j.simpleLogger.levelInBrackets=true
\ No newline at end of file
diff --git a/src/main/java/org/java_websocket/AbstractWebSocket.java b/src/main/java/org/java_websocket/AbstractWebSocket.java
index fce27a114..ae9ce1824 100644
--- a/src/main/java/org/java_websocket/AbstractWebSocket.java
+++ b/src/main/java/org/java_websocket/AbstractWebSocket.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -25,12 +25,16 @@
package org.java_websocket;
-import org.java_websocket.framing.CloseFrame;
-
import java.util.ArrayList;
import java.util.Collection;
-import java.util.Timer;
-import java.util.TimerTask;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import org.java_websocket.framing.CloseFrame;
+import org.java_websocket.util.NamedThreadFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
@@ -38,226 +42,335 @@
*/
public abstract class AbstractWebSocket extends WebSocketAdapter {
- /**
- * Attribute which allows you to deactivate the Nagle's algorithm
- * @since 1.3.3
- */
- private boolean tcpNoDelay;
-
- /**
- * Attribute which allows you to enable/disable the SO_REUSEADDR socket option.
- * @since 1.3.5
- */
- private boolean reuseAddr;
-
- /**
- * Attribute for a timer allowing to check for lost connections
- * @since 1.3.4
- */
- private Timer connectionLostTimer;
- /**
- * Attribute for a timertask allowing to check for lost connections
- * @since 1.3.4
- */
- private TimerTask connectionLostTimerTask;
-
- /**
- * Attribute for the lost connection check interval
- * @since 1.3.4
- */
- private int connectionLostTimeout = 60;
-
- /**
- * Attribute to keep track if the WebSocket Server/Client is running/connected
- * @since 1.3.9
- */
- private boolean websocketRunning = false;
-
- /**
- * Get the interval checking for lost connections
- * Default is 60 seconds
- * @return the interval
- * @since 1.3.4
- */
- public int getConnectionLostTimeout() {
- return connectionLostTimeout;
+ /**
+ * Logger instance
+ *
+ * @since 1.4.0
+ */
+ private final Logger log = LoggerFactory.getLogger(AbstractWebSocket.class);
+
+ /**
+ * Attribute which allows you to deactivate the Nagle's algorithm
+ *
+ * @since 1.3.3
+ */
+ private boolean tcpNoDelay;
+
+ /**
+ * Attribute which allows you to enable/disable the SO_REUSEADDR socket option.
+ *
+ * @since 1.3.5
+ */
+ private boolean reuseAddr;
+
+ /**
+ * Attribute for a service that triggers lost connection checking
+ *
+ * @since 1.4.1
+ */
+ private ScheduledExecutorService connectionLostCheckerService;
+ /**
+ * Attribute for a task that checks for lost connections
+ *
+ * @since 1.4.1
+ */
+ private ScheduledFuture> connectionLostCheckerFuture;
+
+ /**
+ * Attribute for the lost connection check interval in nanoseconds
+ *
+ * @since 1.3.4
+ */
+ private long connectionLostTimeout = TimeUnit.SECONDS.toNanos(60);
+
+ /**
+ * Attribute to keep track if the WebSocket Server/Client is running/connected
+ *
+ * @since 1.3.9
+ */
+ private boolean websocketRunning = false;
+
+ /**
+ * Attribute to start internal threads as daemon
+ *
+ * @since 1.5.6
+ */
+ private boolean daemon = false;
+
+ /**
+ * Attribute to sync on
+ */
+ private final Object syncConnectionLost = new Object();
+
+ /**
+ * TCP receive buffer size that will be used for sockets (zero means use system default)
+ *
+ * @since 1.5.7
+ */
+ private int receiveBufferSize = 0;
+
+ /**
+ * Used for internal buffer allocations when the socket buffer size is not specified.
+ */
+ protected static int DEFAULT_READ_BUFFER_SIZE = 65536;
+
+ /**
+ * Get the interval checking for lost connections Default is 60 seconds
+ *
+ * @return the interval in seconds
+ * @since 1.3.4
+ */
+ public int getConnectionLostTimeout() {
+ synchronized (syncConnectionLost) {
+ return (int) TimeUnit.NANOSECONDS.toSeconds(connectionLostTimeout);
}
+ }
- /**
- * Setter for the interval checking for lost connections
- * A value lower or equal 0 results in the check to be deactivated
- *
- * @param connectionLostTimeout the interval in seconds
- * @since 1.3.4
- */
- public void setConnectionLostTimeout( int connectionLostTimeout ) {
- this.connectionLostTimeout = connectionLostTimeout;
- if (this.connectionLostTimeout <= 0) {
- if( WebSocketImpl.DEBUG )
- System.out.println( "Connection lost timer stopped" );
- cancelConnectionLostTimer();
- return;
+ /**
+ * Setter for the interval checking for lost connections A value lower or equal 0 results in the
+ * check to be deactivated
+ *
+ * @param connectionLostTimeout the interval in seconds
+ * @since 1.3.4
+ */
+ public void setConnectionLostTimeout(int connectionLostTimeout) {
+ synchronized (syncConnectionLost) {
+ this.connectionLostTimeout = TimeUnit.SECONDS.toNanos(connectionLostTimeout);
+ if (this.connectionLostTimeout <= 0) {
+ log.trace("Connection lost timer stopped");
+ cancelConnectionLostTimer();
+ return;
+ }
+ if (this.websocketRunning) {
+ log.trace("Connection lost timer restarted");
+ //Reset all the pings
+ try {
+ ArrayList connections = new ArrayList<>(getConnections());
+ WebSocketImpl webSocketImpl;
+ for (WebSocket conn : connections) {
+ if (conn instanceof WebSocketImpl) {
+ webSocketImpl = (WebSocketImpl) conn;
+ webSocketImpl.updateLastPong();
+ }
+ }
+ } catch (Exception e) {
+ log.error("Exception during connection lost restart", e);
}
- if (this.websocketRunning) {
- if( WebSocketImpl.DEBUG )
- System.out.println( "Connection lost timer restarted" );
- //Reset all the pings
- try {
- ArrayList connections = new ArrayList( getConnections() );
- WebSocketImpl webSocketImpl;
- for( WebSocket conn : connections ) {
- if( conn instanceof WebSocketImpl ) {
- webSocketImpl = ( WebSocketImpl ) conn;
- webSocketImpl.updateLastPong();
- }
- }
- } catch (Exception e) {
- if (WebSocketImpl.DEBUG)
- System.out.println("Exception during connection lost restart: " + e.getMessage());
- }
- restartConnectionLostTimer();
- }
+ restartConnectionLostTimer();
+ }
}
+ }
- /**
- * Stop the connection lost timer
- * @since 1.3.4
- */
- protected void stopConnectionLostTimer() {
- if (connectionLostTimer != null ||connectionLostTimerTask != null) {
- this.websocketRunning = false;
- if( WebSocketImpl.DEBUG )
- System.out.println( "Connection lost timer stopped" );
- cancelConnectionLostTimer();
- }
+ /**
+ * Stop the connection lost timer
+ *
+ * @since 1.3.4
+ */
+ protected void stopConnectionLostTimer() {
+ synchronized (syncConnectionLost) {
+ if (connectionLostCheckerService != null || connectionLostCheckerFuture != null) {
+ this.websocketRunning = false;
+ log.trace("Connection lost timer stopped");
+ cancelConnectionLostTimer();
+ }
}
- /**
- * Start the connection lost timer
- * @since 1.3.4
- */
- protected void startConnectionLostTimer() {
- if (this.connectionLostTimeout <= 0) {
- if (WebSocketImpl.DEBUG)
- System.out.println("Connection lost timer deactivated");
- return;
- }
- if (WebSocketImpl.DEBUG)
- System.out.println("Connection lost timer started");
- this.websocketRunning = true;
- restartConnectionLostTimer();
+ }
+
+ /**
+ * Start the connection lost timer
+ *
+ * @since 1.3.4
+ */
+ protected void startConnectionLostTimer() {
+ synchronized (syncConnectionLost) {
+ if (this.connectionLostTimeout <= 0) {
+ log.trace("Connection lost timer deactivated");
+ return;
+ }
+ log.trace("Connection lost timer started");
+ this.websocketRunning = true;
+ restartConnectionLostTimer();
}
+ }
- /**
- * This methods allows the reset of the connection lost timer in case of a changed parameter
- * @since 1.3.4
- */
- private void restartConnectionLostTimer() {
- cancelConnectionLostTimer();
- connectionLostTimer = new Timer("WebSocketTimer");
- connectionLostTimerTask = new TimerTask() {
-
- /**
- * Keep the connections in a separate list to not cause deadlocks
- */
- private ArrayList connections = new ArrayList( );
- @Override
- public void run() {
- connections.clear();
- try {
- connections.addAll( getConnections() );
- long current = ( System.currentTimeMillis() - ( connectionLostTimeout * 1500 ) );
- WebSocketImpl webSocketImpl;
- for( WebSocket conn : connections ) {
- if( conn instanceof WebSocketImpl ) {
- webSocketImpl = ( WebSocketImpl ) conn;
- if( webSocketImpl.getLastPong() < current ) {
- if( WebSocketImpl.DEBUG )
- System.out.println( "Closing connection due to no pong received: " + conn.toString() );
- webSocketImpl.closeConnection( CloseFrame.ABNORMAL_CLOSE, "The connection was closed because the other endpoint did not respond with a pong in time. For more information check: https://github.com/TooTallNate/Java-WebSocket/wiki/Lost-connection-detection" );
- } else {
- if( webSocketImpl.isOpen() ) {
- webSocketImpl.sendPing();
- } else {
- if( WebSocketImpl.DEBUG )
- System.out.println( "Trying to ping a non open connection: " + conn.toString() );
- }
- }
- }
- }
- } catch ( Exception e ) {
- if (WebSocketImpl.DEBUG)
- System.out.println("Exception during connection lost ping: " + e.getMessage());
- }
- connections.clear();
- }
- };
- connectionLostTimer.scheduleAtFixedRate( connectionLostTimerTask,connectionLostTimeout * 1000, connectionLostTimeout * 1000 );
- }
-
- /**
- * Getter to get all the currently available connections
- * @return the currently available connections
- * @since 1.3.4
- */
- protected abstract Collection getConnections();
-
- /**
- * Cancel any running timer for the connection lost detection
- * @since 1.3.4
- */
- private void cancelConnectionLostTimer() {
- if( connectionLostTimer != null ) {
- connectionLostTimer.cancel();
- connectionLostTimer = null;
- }
- if( connectionLostTimerTask != null ) {
- connectionLostTimerTask.cancel();
- connectionLostTimerTask = null;
+ /**
+ * This methods allows the reset of the connection lost timer in case of a changed parameter
+ *
+ * @since 1.3.4
+ */
+ private void restartConnectionLostTimer() {
+ cancelConnectionLostTimer();
+ connectionLostCheckerService = Executors
+ .newSingleThreadScheduledExecutor(new NamedThreadFactory("WebSocketConnectionLostChecker", daemon));
+ Runnable connectionLostChecker = new Runnable() {
+
+ /**
+ * Keep the connections in a separate list to not cause deadlocks
+ */
+ private ArrayList connections = new ArrayList<>();
+
+ @Override
+ public void run() {
+ connections.clear();
+ try {
+ connections.addAll(getConnections());
+ long minimumPongTime;
+ synchronized (syncConnectionLost) {
+ minimumPongTime = (long) (System.nanoTime() - (connectionLostTimeout * 1.5));
+ }
+ for (WebSocket conn : connections) {
+ executeConnectionLostDetection(conn, minimumPongTime);
+ }
+ } catch (Exception e) {
+ //Ignore this exception
}
- }
+ connections.clear();
+ }
+ };
- /**
- * Tests if TCP_NODELAY is enabled.
- *
- * @return a boolean indicating whether or not TCP_NODELAY is enabled for new connections.
- * @since 1.3.3
- */
- public boolean isTcpNoDelay() {
- return tcpNoDelay;
+ connectionLostCheckerFuture = connectionLostCheckerService
+ .scheduleAtFixedRate(connectionLostChecker, connectionLostTimeout, connectionLostTimeout,
+ TimeUnit.NANOSECONDS);
+ }
+
+ /**
+ * Send a ping to the endpoint or close the connection since the other endpoint did not respond
+ * with a ping
+ *
+ * @param webSocket the websocket instance
+ * @param minimumPongTime the lowest/oldest allowable last pong time (in nanoTime) before we
+ * consider the connection to be lost
+ */
+ private void executeConnectionLostDetection(WebSocket webSocket, long minimumPongTime) {
+ if (!(webSocket instanceof WebSocketImpl)) {
+ return;
+ }
+ WebSocketImpl webSocketImpl = (WebSocketImpl) webSocket;
+ if (webSocketImpl.getLastPong() < minimumPongTime) {
+ log.trace("Closing connection due to no pong received: {}", webSocketImpl);
+ webSocketImpl.closeConnection(CloseFrame.ABNORMAL_CLOSE,
+ "The connection was closed because the other endpoint did not respond with a pong in time. For more information check: https://github.com/TooTallNate/Java-WebSocket/wiki/Lost-connection-detection");
+ } else {
+ if (webSocketImpl.isOpen()) {
+ webSocketImpl.sendPing();
+ } else {
+ log.trace("Trying to ping a non open connection: {}", webSocketImpl);
+ }
}
+ }
- /**
- * Setter for tcpNoDelay
- *
- * Enable/disable TCP_NODELAY (disable/enable Nagle's algorithm) for new connections
- *
- * @param tcpNoDelay true to enable TCP_NODELAY, false to disable.
- * @since 1.3.3
- */
- public void setTcpNoDelay( boolean tcpNoDelay ) {
- this.tcpNoDelay = tcpNoDelay;
+ /**
+ * Getter to get all the currently available connections
+ *
+ * @return the currently available connections
+ * @since 1.3.4
+ */
+ protected abstract Collection getConnections();
+
+ /**
+ * Cancel any running timer for the connection lost detection
+ *
+ * @since 1.3.4
+ */
+ private void cancelConnectionLostTimer() {
+ if (connectionLostCheckerService != null) {
+ connectionLostCheckerService.shutdownNow();
+ connectionLostCheckerService = null;
+ }
+ if (connectionLostCheckerFuture != null) {
+ connectionLostCheckerFuture.cancel(false);
+ connectionLostCheckerFuture = null;
}
+ }
- /**
- * Tests Tests if SO_REUSEADDR is enabled.
- *
- * @return a boolean indicating whether or not SO_REUSEADDR is enabled.
- * @since 1.3.5
- */
- public boolean isReuseAddr() {
- return reuseAddr;
- }
-
- /**
- * Setter for soReuseAddr
- *
- * Enable/disable SO_REUSEADDR for the socket
- *
- * @param reuseAddr whether to enable or disable SO_REUSEADDR
- * @since 1.3.5
- */
- public void setReuseAddr( boolean reuseAddr ) {
- this.reuseAddr = reuseAddr;
- }
+ /**
+ * Tests if TCP_NODELAY is enabled.
+ *
+ * @return a boolean indicating whether or not TCP_NODELAY is enabled for new connections.
+ * @since 1.3.3
+ */
+ public boolean isTcpNoDelay() {
+ return tcpNoDelay;
+ }
+
+ /**
+ * Setter for tcpNoDelay
+ *
+ * Enable/disable TCP_NODELAY (disable/enable Nagle's algorithm) for new connections
+ *
+ * @param tcpNoDelay true to enable TCP_NODELAY, false to disable.
+ * @since 1.3.3
+ */
+ public void setTcpNoDelay(boolean tcpNoDelay) {
+ this.tcpNoDelay = tcpNoDelay;
+ }
+
+ /**
+ * Tests Tests if SO_REUSEADDR is enabled.
+ *
+ * @return a boolean indicating whether or not SO_REUSEADDR is enabled.
+ * @since 1.3.5
+ */
+ public boolean isReuseAddr() {
+ return reuseAddr;
+ }
+
+ /**
+ * Setter for soReuseAddr
+ *
+ * Enable/disable SO_REUSEADDR for the socket
+ *
+ * @param reuseAddr whether to enable or disable SO_REUSEADDR
+ * @since 1.3.5
+ */
+ public void setReuseAddr(boolean reuseAddr) {
+ this.reuseAddr = reuseAddr;
+ }
+
+
+ /**
+ * Getter for daemon
+ *
+ * @return whether internal threads are spawned in daemon mode
+ * @since 1.5.6
+ */
+ public boolean isDaemon() {
+ return daemon;
+ }
+
+ /**
+ * Setter for daemon
+ *
+ * Controls whether or not internal threads are spawned in daemon mode
+ *
+ * @since 1.5.6
+ */
+ public void setDaemon(boolean daemon) {
+ this.daemon = daemon;
+ }
+
+ /**
+ * Returns the TCP receive buffer size that will be used for sockets (or zero, if not explicitly set).
+ * @see java.net.Socket#setReceiveBufferSize(int)
+ *
+ * @since 1.5.7
+ */
+ public int getReceiveBufferSize() {
+ return receiveBufferSize;
+ }
+
+ /**
+ * Sets the TCP receive buffer size that will be used for sockets.
+ * If this is not explicitly set (or set to zero), the system default is used.
+ * @see java.net.Socket#setReceiveBufferSize(int)
+ *
+ * @since 1.5.7
+ */
+ public void setReceiveBufferSize(int receiveBufferSize) {
+ if (receiveBufferSize < 0) {
+ throw new IllegalArgumentException("buffer size < 0");
+ }
+ this.receiveBufferSize = receiveBufferSize;
+ }
}
diff --git a/src/main/java/org/java_websocket/AbstractWrappedByteChannel.java b/src/main/java/org/java_websocket/AbstractWrappedByteChannel.java
index 2ec9ba2dc..33e6ddcc9 100644
--- a/src/main/java/org/java_websocket/AbstractWrappedByteChannel.java
+++ b/src/main/java/org/java_websocket/AbstractWrappedByteChannel.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -30,68 +30,82 @@
import java.nio.channels.ByteChannel;
import java.nio.channels.SocketChannel;
+/**
+ * @deprecated
+ */
+@Deprecated
public class AbstractWrappedByteChannel implements WrappedByteChannel {
- private final ByteChannel channel;
-
- public AbstractWrappedByteChannel( ByteChannel towrap ) {
- this.channel = towrap;
- }
-
- public AbstractWrappedByteChannel( WrappedByteChannel towrap ) {
- this.channel = towrap;
- }
-
- @Override
- public int read( ByteBuffer dst ) throws IOException {
- return channel.read( dst );
- }
-
- @Override
- public boolean isOpen() {
- return channel.isOpen();
- }
-
- @Override
- public void close() throws IOException {
- channel.close();
- }
-
- @Override
- public int write( ByteBuffer src ) throws IOException {
- return channel.write( src );
- }
-
- @Override
- public boolean isNeedWrite() {
- return channel instanceof WrappedByteChannel && ((WrappedByteChannel) channel).isNeedWrite();
- }
-
- @Override
- public void writeMore() throws IOException {
- if( channel instanceof WrappedByteChannel )
- ( (WrappedByteChannel) channel ).writeMore();
-
- }
-
- @Override
- public boolean isNeedRead() {
- return channel instanceof WrappedByteChannel && ((WrappedByteChannel) channel).isNeedRead();
-
- }
-
- @Override
- public int readMore( ByteBuffer dst ) throws IOException {
- return channel instanceof WrappedByteChannel ? ( (WrappedByteChannel) channel ).readMore( dst ) : 0;
- }
-
- @Override
- public boolean isBlocking() {
- if( channel instanceof SocketChannel )
- return ( (SocketChannel) channel ).isBlocking();
- else if( channel instanceof WrappedByteChannel )
- return ( (WrappedByteChannel) channel ).isBlocking();
- return false;
- }
+ private final ByteChannel channel;
+
+ /**
+ * @deprecated
+ */
+ @Deprecated
+ public AbstractWrappedByteChannel(ByteChannel towrap) {
+ this.channel = towrap;
+ }
+
+ /**
+ * @deprecated
+ */
+ @Deprecated
+ public AbstractWrappedByteChannel(WrappedByteChannel towrap) {
+ this.channel = towrap;
+ }
+
+ @Override
+ public int read(ByteBuffer dst) throws IOException {
+ return channel.read(dst);
+ }
+
+ @Override
+ public boolean isOpen() {
+ return channel.isOpen();
+ }
+
+ @Override
+ public void close() throws IOException {
+ channel.close();
+ }
+
+ @Override
+ public int write(ByteBuffer src) throws IOException {
+ return channel.write(src);
+ }
+
+ @Override
+ public boolean isNeedWrite() {
+ return channel instanceof WrappedByteChannel && ((WrappedByteChannel) channel).isNeedWrite();
+ }
+
+ @Override
+ public void writeMore() throws IOException {
+ if (channel instanceof WrappedByteChannel) {
+ ((WrappedByteChannel) channel).writeMore();
+ }
+
+ }
+
+ @Override
+ public boolean isNeedRead() {
+ return channel instanceof WrappedByteChannel && ((WrappedByteChannel) channel).isNeedRead();
+
+ }
+
+ @Override
+ public int readMore(ByteBuffer dst) throws IOException {
+ return channel instanceof WrappedByteChannel ? ((WrappedByteChannel) channel).readMore(dst) : 0;
+ }
+
+ @Override
+ public boolean isBlocking() {
+ if (channel instanceof SocketChannel) {
+ return ((SocketChannel) channel).isBlocking();
+ } else if (channel instanceof WrappedByteChannel) {
+ return ((WrappedByteChannel) channel).isBlocking();
+ }
+ return false;
+ }
}
diff --git a/src/main/java/org/java_websocket/SSLSocketChannel.java b/src/main/java/org/java_websocket/SSLSocketChannel.java
index 3d2ef38b1..b4024505b 100644
--- a/src/main/java/org/java_websocket/SSLSocketChannel.java
+++ b/src/main/java/org/java_websocket/SSLSocketChannel.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -25,13 +25,6 @@
package org.java_websocket;
-import org.java_websocket.util.ByteBufferUtils;
-
-import javax.net.ssl.SSLEngine;
-import javax.net.ssl.SSLEngineResult;
-import javax.net.ssl.SSLEngineResult.HandshakeStatus;
-import javax.net.ssl.SSLException;
-import javax.net.ssl.SSLSession;
import java.io.IOException;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
@@ -39,462 +32,508 @@
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.concurrent.ExecutorService;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLEngineResult.HandshakeStatus;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLSession;
+import org.java_websocket.interfaces.ISSLChannel;
+import org.java_websocket.util.ByteBufferUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* A class that represents an SSL/TLS peer, and can be extended to create a client or a server.
- *
- * It makes use of the JSSE framework, and specifically the {@link SSLEngine} logic, which
- * is described by Oracle as "an advanced API, not appropriate for casual use", since
- * it requires the user to implement much of the communication establishment procedure himself.
- * More information about it can be found here: http://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#SSLEngine
- *
- * {@link SSLSocketChannel} implements the handshake protocol, required to establish a connection between two peers,
- * which is common for both client and server and provides the abstract {@link SSLSocketChannel#read(ByteBuffer)} and
- * {@link SSLSocketChannel#write(ByteBuffer)} (String)} methods, that need to be implemented by the specific SSL/TLS peer
- * that is going to extend this class.
+ *
+ * It makes use of the JSSE framework, and specifically the {@link SSLEngine} logic, which is
+ * described by Oracle as "an advanced API, not appropriate for casual use", since it requires the
+ * user to implement much of the communication establishment procedure himself. More information
+ * about it can be found here: http://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#SSLEngine
+ *
+ * {@link SSLSocketChannel} implements the handshake protocol, required to establish a connection
+ * between two peers, which is common for both client and server and provides the abstract {@link
+ * SSLSocketChannel#read(ByteBuffer)} and {@link SSLSocketChannel#write(ByteBuffer)} (String)}
+ * methods, that need to be implemented by the specific SSL/TLS peer that is going to extend this
+ * class.
*
* @author Alex Karnezis
- *
- * Modified by marci4 to allow the usage as a ByteChannel
- *
- * Permission for usage recieved at May 25, 2017 by Alex Karnezis
+ *
+ * Modified by marci4 to allow the usage as a ByteChannel
+ *
+ * Permission for usage received at May 25, 2017 by Alex Karnezis
*/
-public class SSLSocketChannel implements WrappedByteChannel, ByteChannel {
-
- /**
- * The underlaying socket channel
- */
- private final SocketChannel socketChannel;
-
- /**
- * The engine which will be used for un-/wrapping of buffers
- */
- private final SSLEngine engine;
-
-
- /**
- * Will contain this peer's application data in plaintext, that will be later encrypted
- * using {@link SSLEngine#wrap(ByteBuffer, ByteBuffer)} and sent to the other peer. This buffer can typically
- * be of any size, as long as it is large enough to contain this peer's outgoing messages.
- * If this peer tries to send a message bigger than buffer's capacity a {@link BufferOverflowException}
- * will be thrown.
- */
- private ByteBuffer myAppData;
-
- /**
- * Will contain this peer's encrypted data, that will be generated after {@link SSLEngine#wrap(ByteBuffer, ByteBuffer)}
- * is applied on {@link SSLSocketChannel#myAppData}. It should be initialized using {@link SSLSession#getPacketBufferSize()},
- * which returns the size up to which, SSL/TLS packets will be generated from the engine under a session.
- * All SSLEngine network buffers should be sized at least this large to avoid insufficient space problems when performing wrap and unwrap calls.
- */
- private ByteBuffer myNetData;
-
- /**
- * Will contain the other peer's (decrypted) application data. It must be large enough to hold the application data
- * from any peer. Can be initialized with {@link SSLSession#getApplicationBufferSize()} for an estimation
- * of the other peer's application data and should be enlarged if this size is not enough.
- */
- private ByteBuffer peerAppData;
-
- /**
- * Will contain the other peer's encrypted data. The SSL/TLS protocols specify that implementations should produce packets containing at most 16 KB of plaintext,
- * so a buffer sized to this value should normally cause no capacity problems. However, some implementations violate the specification and generate large records up to 32 KB.
- * If the {@link SSLEngine#unwrap(ByteBuffer, ByteBuffer)} detects large inbound packets, the buffer sizes returned by SSLSession will be updated dynamically, so the this peer
- * should check for overflow conditions and enlarge the buffer using the session's (updated) buffer size.
- */
- private ByteBuffer peerNetData;
-
- /**
- * Will be used to execute tasks that may emerge during handshake in parallel with the server's main thread.
- */
- private ExecutorService executor;
-
-
- public SSLSocketChannel( SocketChannel inputSocketChannel, SSLEngine inputEngine, ExecutorService inputExecutor, SelectionKey key ) throws IOException {
- if( inputSocketChannel == null || inputEngine == null || executor == inputExecutor )
- throw new IllegalArgumentException( "parameter must not be null" );
-
- this.socketChannel = inputSocketChannel;
- this.engine = inputEngine;
- this.executor = inputExecutor;
- myNetData = ByteBuffer.allocate( engine.getSession().getPacketBufferSize() );
- peerNetData = ByteBuffer.allocate( engine.getSession().getPacketBufferSize() );
- this.engine.beginHandshake();
- if( doHandshake() ) {
- if( key != null ) {
- key.interestOps( key.interestOps() | SelectionKey.OP_WRITE );
- }
- } else {
- try {
- socketChannel.close();
- } catch ( IOException e ) {
- e.printStackTrace();
- }
- }
- }
-
- @Override
- public synchronized int read( ByteBuffer dst ) throws IOException {
- if( !dst.hasRemaining() ) {
- return 0;
- }
- if( peerAppData.hasRemaining() ) {
- peerAppData.flip();
- return ByteBufferUtils.transferByteBuffer( peerAppData, dst );
- }
- peerNetData.compact();
-
- int bytesRead = socketChannel.read( peerNetData );
- /*
- * If bytesRead are 0 put we still have some data in peerNetData still to an unwrap (for testcase 1.1.6)
- */
- if( bytesRead > 0 || peerNetData.hasRemaining() ) {
- peerNetData.flip();
- while( peerNetData.hasRemaining() ) {
- peerAppData.compact();
- SSLEngineResult result;
- try {
- result = engine.unwrap( peerNetData, peerAppData );
- } catch ( SSLException e ) {
- e.printStackTrace();
- throw e;
- }
- switch(result.getStatus()) {
- case OK:
- peerAppData.flip();
- return ByteBufferUtils.transferByteBuffer( peerAppData, dst );
- case BUFFER_UNDERFLOW:
- peerAppData.flip();
- return ByteBufferUtils.transferByteBuffer( peerAppData, dst );
- case BUFFER_OVERFLOW:
- peerAppData = enlargeApplicationBuffer( peerAppData );
- break;
- case CLOSED:
- closeConnection();
- dst.clear();
- return -1;
- default:
- throw new IllegalStateException( "Invalid SSL status: " + result.getStatus() );
- }
- }
- } else if( bytesRead < 0 ) {
- handleEndOfStream();
- }
- ByteBufferUtils.transferByteBuffer( peerAppData, dst );
- return bytesRead;
- }
-
- @Override
- public synchronized int write( ByteBuffer output ) throws IOException {
- int num = 0;
- while( output.hasRemaining() ) {
- // The loop has a meaning for (outgoing) messages larger than 16KB.
- // Every wrap call will remove 16KB from the original message and send it to the remote peer.
- myNetData.clear();
- SSLEngineResult result = engine.wrap( output, myNetData );
- switch(result.getStatus()) {
- case OK:
- myNetData.flip();
- while( myNetData.hasRemaining() ) {
- num += socketChannel.write( myNetData );
- }
- break;
- case BUFFER_OVERFLOW:
- myNetData = enlargePacketBuffer( myNetData );
- break;
- case BUFFER_UNDERFLOW:
- throw new SSLException( "Buffer underflow occured after a wrap. I don't think we should ever get here." );
- case CLOSED:
- closeConnection();
- return 0;
- default:
- throw new IllegalStateException( "Invalid SSL status: " + result.getStatus() );
- }
- }
- return num;
- }
-
- /**
- * Implements the handshake protocol between two peers, required for the establishment of the SSL/TLS connection.
- * During the handshake, encryption configuration information - such as the list of available cipher suites - will be exchanged
- * and if the handshake is successful will lead to an established SSL/TLS session.
- *
- *
- * A typical handshake will usually contain the following steps:
- *
- *
- * - 1. wrap: ClientHello
- * - 2. unwrap: ServerHello/Cert/ServerHelloDone
- * - 3. wrap: ClientKeyExchange
- * - 4. wrap: ChangeCipherSpec
- * - 5. wrap: Finished
- * - 6. unwrap: ChangeCipherSpec
- * - 7. unwrap: Finished
- *
- *
- * Handshake is also used during the end of the session, in order to properly close the connection between the two peers.
- * A proper connection close will typically include the one peer sending a CLOSE message to another, and then wait for
- * the other's CLOSE message to close the transport link. The other peer from his perspective would read a CLOSE message
- * from his peer and then enter the handshake procedure to send his own CLOSE message as well.
- *
- * @return True if the connection handshake was successful or false if an error occurred.
- * @throws IOException - if an error occurs during read/write to the socket channel.
- */
- private boolean doHandshake() throws IOException {
- SSLEngineResult result;
- HandshakeStatus handshakeStatus;
-
- // NioSslPeer's fields myAppData and peerAppData are supposed to be large enough to hold all message data the peer
- // will send and expects to receive from the other peer respectively. Since the messages to be exchanged will usually be less
- // than 16KB long the capacity of these fields should also be smaller. Here we initialize these two local buffers
- // to be used for the handshake, while keeping client's buffers at the same size.
- int appBufferSize = engine.getSession().getApplicationBufferSize();
- myAppData = ByteBuffer.allocate( appBufferSize );
- peerAppData = ByteBuffer.allocate( appBufferSize );
- myNetData.clear();
- peerNetData.clear();
-
- handshakeStatus = engine.getHandshakeStatus();
- while( handshakeStatus != SSLEngineResult.HandshakeStatus.FINISHED && handshakeStatus != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING ) {
- switch(handshakeStatus) {
- case NEED_UNWRAP:
- if( socketChannel.read( peerNetData ) < 0 ) {
- if( engine.isInboundDone() && engine.isOutboundDone() ) {
- return false;
- }
- try {
- engine.closeInbound();
- } catch ( SSLException e ) {
- //Ignore, cant do anything against this exception
- }
- engine.closeOutbound();
- // After closeOutbound the engine will be set to WRAP state, in order to try to send a close message to the client.
- handshakeStatus = engine.getHandshakeStatus();
- break;
- }
- peerNetData.flip();
- try {
- result = engine.unwrap( peerNetData, peerAppData );
- peerNetData.compact();
- handshakeStatus = result.getHandshakeStatus();
- } catch ( SSLException sslException ) {
- engine.closeOutbound();
- handshakeStatus = engine.getHandshakeStatus();
- break;
- }
- switch(result.getStatus()) {
- case OK:
- break;
- case BUFFER_OVERFLOW:
- // Will occur when peerAppData's capacity is smaller than the data derived from peerNetData's unwrap.
- peerAppData = enlargeApplicationBuffer( peerAppData );
- break;
- case BUFFER_UNDERFLOW:
- // Will occur either when no data was read from the peer or when the peerNetData buffer was too small to hold all peer's data.
- peerNetData = handleBufferUnderflow( peerNetData );
- break;
- case CLOSED:
- if( engine.isOutboundDone() ) {
- return false;
- } else {
- engine.closeOutbound();
- handshakeStatus = engine.getHandshakeStatus();
- break;
- }
- default:
- throw new IllegalStateException( "Invalid SSL status: " + result.getStatus() );
- }
- break;
- case NEED_WRAP:
- myNetData.clear();
- try {
- result = engine.wrap( myAppData, myNetData );
- handshakeStatus = result.getHandshakeStatus();
- } catch ( SSLException sslException ) {
- engine.closeOutbound();
- handshakeStatus = engine.getHandshakeStatus();
- break;
- }
- switch(result.getStatus()) {
- case OK:
- myNetData.flip();
- while( myNetData.hasRemaining() ) {
- socketChannel.write( myNetData );
- }
- break;
- case BUFFER_OVERFLOW:
- // Will occur if there is not enough space in myNetData buffer to write all the data that would be generated by the method wrap.
- // Since myNetData is set to session's packet size we should not get to this point because SSLEngine is supposed
- // to produce messages smaller or equal to that, but a general handling would be the following:
- myNetData = enlargePacketBuffer( myNetData );
- break;
- case BUFFER_UNDERFLOW:
- throw new SSLException( "Buffer underflow occured after a wrap. I don't think we should ever get here." );
- case CLOSED:
- try {
- myNetData.flip();
- while( myNetData.hasRemaining() ) {
- socketChannel.write( myNetData );
- }
- // At this point the handshake status will probably be NEED_UNWRAP so we make sure that peerNetData is clear to read.
- peerNetData.clear();
- } catch ( Exception e ) {
- handshakeStatus = engine.getHandshakeStatus();
- }
- break;
- default:
- throw new IllegalStateException( "Invalid SSL status: " + result.getStatus() );
- }
- break;
- case NEED_TASK:
- Runnable task;
- while( ( task = engine.getDelegatedTask() ) != null ) {
- executor.execute( task );
- }
- handshakeStatus = engine.getHandshakeStatus();
- break;
- case FINISHED:
- break;
- case NOT_HANDSHAKING:
- break;
- default:
- throw new IllegalStateException( "Invalid SSL status: " + handshakeStatus );
- }
- }
-
- return true;
-
- }
-
- /**
- * Enlarging a packet buffer (peerNetData or myNetData)
- *
- * @param buffer the buffer to enlarge
- * @return the enlarged buffer
- */
- private ByteBuffer enlargePacketBuffer( ByteBuffer buffer ) {
- return enlargeBuffer( buffer, engine.getSession().getPacketBufferSize() );
- }
-
- /**
- * Enlarging a packet buffer (peerAppData or myAppData)
- *
- * @param buffer the buffer to enlarge
- * @return the enlarged buffer
- */
- private ByteBuffer enlargeApplicationBuffer( ByteBuffer buffer ) {
- return enlargeBuffer( buffer, engine.getSession().getApplicationBufferSize() );
- }
-
- /**
- * Compares sessionProposedCapacity with buffer's capacity. If buffer's capacity is smaller,
- * returns a buffer with the proposed capacity. If it's equal or larger, returns a buffer
- * with capacity twice the size of the initial one.
- *
- * @param buffer - the buffer to be enlarged.
- * @param sessionProposedCapacity - the minimum size of the new buffer, proposed by {@link SSLSession}.
- * @return A new buffer with a larger capacity.
- */
- private ByteBuffer enlargeBuffer( ByteBuffer buffer, int sessionProposedCapacity ) {
- if( sessionProposedCapacity > buffer.capacity() ) {
- buffer = ByteBuffer.allocate( sessionProposedCapacity );
- } else {
- buffer = ByteBuffer.allocate( buffer.capacity() * 2 );
- }
- return buffer;
- }
-
- /**
- * Handles {@link SSLEngineResult.Status#BUFFER_UNDERFLOW}. Will check if the buffer is already filled, and if there is no space problem
- * will return the same buffer, so the client tries to read again. If the buffer is already filled will try to enlarge the buffer either to
- * session's proposed size or to a larger capacity. A buffer underflow can happen only after an unwrap, so the buffer will always be a
- * peerNetData buffer.
- *
- * @param buffer - will always be peerNetData buffer.
- * @return The same buffer if there is no space problem or a new buffer with the same data but more space.
- */
- private ByteBuffer handleBufferUnderflow( ByteBuffer buffer ) {
- if( engine.getSession().getPacketBufferSize() < buffer.limit() ) {
- return buffer;
- } else {
- ByteBuffer replaceBuffer = enlargePacketBuffer( buffer );
- buffer.flip();
- replaceBuffer.put( buffer );
- return replaceBuffer;
- }
- }
-
- /**
- * This method should be called when this peer wants to explicitly close the connection
- * or when a close message has arrived from the other peer, in order to provide an orderly shutdown.
- *
- * It first calls {@link SSLEngine#closeOutbound()} which prepares this peer to send its own close message and
- * sets {@link SSLEngine} to the NEED_WRAP state. Then, it delegates the exchange of close messages
- * to the handshake method and finally, it closes socket channel.
- *
- * @throws IOException if an I/O error occurs to the socket channel.
- */
- private void closeConnection() throws IOException {
- engine.closeOutbound();
- try {
- doHandshake();
- } catch ( IOException e ) {
- //Just ignore this exception since we are closing the connection already
- }
- socketChannel.close();
- }
-
- /**
- * In addition to orderly shutdowns, an unorderly shutdown may occur, when the transport link (socket channel)
- * is severed before close messages are exchanged. This may happen by getting an -1 or {@link IOException}
- * when trying to read from the socket channel, or an {@link IOException} when trying to write to it.
- * In both cases {@link SSLEngine#closeInbound()} should be called and then try to follow the standard procedure.
- *
- * @throws IOException if an I/O error occurs to the socket channel.
- */
- private void handleEndOfStream() throws IOException {
- try {
- engine.closeInbound();
- } catch ( Exception e ) {
- System.err.println( "This engine was forced to close inbound, without having received the proper SSL/TLS close notification message from the peer, due to end of stream." );
- }
- closeConnection();
- }
-
- @Override
- public boolean isNeedWrite() {
- return false;
- }
-
- @Override
- public void writeMore() throws IOException {
- //Nothing to do since we write out all the data in a while loop
- }
-
- @Override
- public boolean isNeedRead() {
- return peerNetData.hasRemaining() || peerAppData.hasRemaining();
- }
-
- @Override
- public int readMore( ByteBuffer dst ) throws IOException {
- return read( dst );
- }
-
- @Override
- public boolean isBlocking() {
- return socketChannel.isBlocking();
- }
-
-
- @Override
- public boolean isOpen() {
- return socketChannel.isOpen();
- }
-
- @Override
- public void close() throws IOException {
- closeConnection();
- }
+public class SSLSocketChannel implements WrappedByteChannel, ByteChannel, ISSLChannel {
+
+ /**
+ * Logger instance
+ *
+ * @since 1.4.0
+ */
+ private final Logger log = LoggerFactory.getLogger(SSLSocketChannel.class);
+
+ /**
+ * The underlying socket channel
+ */
+ private final SocketChannel socketChannel;
+
+ /**
+ * The engine which will be used for un-/wrapping of buffers
+ */
+ private final SSLEngine engine;
+
+
+ /**
+ * Will contain this peer's application data in plaintext, that will be later encrypted using
+ * {@link SSLEngine#wrap(ByteBuffer, ByteBuffer)} and sent to the other peer. This buffer can
+ * typically be of any size, as long as it is large enough to contain this peer's outgoing
+ * messages. If this peer tries to send a message bigger than buffer's capacity a {@link
+ * BufferOverflowException} will be thrown.
+ */
+ private ByteBuffer myAppData;
+
+ /**
+ * Will contain this peer's encrypted data, that will be generated after {@link
+ * SSLEngine#wrap(ByteBuffer, ByteBuffer)} is applied on {@link SSLSocketChannel#myAppData}. It
+ * should be initialized using {@link SSLSession#getPacketBufferSize()}, which returns the size up
+ * to which, SSL/TLS packets will be generated from the engine under a session. All SSLEngine
+ * network buffers should be sized at least this large to avoid insufficient space problems when
+ * performing wrap and unwrap calls.
+ */
+ private ByteBuffer myNetData;
+
+ /**
+ * Will contain the other peer's (decrypted) application data. It must be large enough to hold the
+ * application data from any peer. Can be initialized with {@link SSLSession#getApplicationBufferSize()}
+ * for an estimation of the other peer's application data and should be enlarged if this size is
+ * not enough.
+ */
+ private ByteBuffer peerAppData;
+
+ /**
+ * Will contain the other peer's encrypted data. The SSL/TLS protocols specify that
+ * implementations should produce packets containing at most 16 KB of plaintext, so a buffer sized
+ * to this value should normally cause no capacity problems. However, some implementations violate
+ * the specification and generate large records up to 32 KB. If the {@link
+ * SSLEngine#unwrap(ByteBuffer, ByteBuffer)} detects large inbound packets, the buffer sizes
+ * returned by SSLSession will be updated dynamically, so the this peer should check for overflow
+ * conditions and enlarge the buffer using the session's (updated) buffer size.
+ */
+ private ByteBuffer peerNetData;
+
+ /**
+ * Will be used to execute tasks that may emerge during handshake in parallel with the server's
+ * main thread.
+ */
+ private ExecutorService executor;
+
+
+ public SSLSocketChannel(SocketChannel inputSocketChannel, SSLEngine inputEngine,
+ ExecutorService inputExecutor, SelectionKey key) throws IOException {
+ if (inputSocketChannel == null || inputEngine == null || executor == inputExecutor) {
+ throw new IllegalArgumentException("parameter must not be null");
+ }
+
+ this.socketChannel = inputSocketChannel;
+ this.engine = inputEngine;
+ this.executor = inputExecutor;
+ myNetData = ByteBuffer.allocate(engine.getSession().getPacketBufferSize());
+ peerNetData = ByteBuffer.allocate(engine.getSession().getPacketBufferSize());
+ this.engine.beginHandshake();
+ if (doHandshake()) {
+ if (key != null) {
+ key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);
+ }
+ } else {
+ try {
+ socketChannel.close();
+ } catch (IOException e) {
+ log.error("Exception during the closing of the channel", e);
+ }
+ }
+ }
+
+ @Override
+ public synchronized int read(ByteBuffer dst) throws IOException {
+ if (!dst.hasRemaining()) {
+ return 0;
+ }
+ if (peerAppData.hasRemaining()) {
+ peerAppData.flip();
+ return ByteBufferUtils.transferByteBuffer(peerAppData, dst);
+ }
+ peerNetData.compact();
+
+ int bytesRead = socketChannel.read(peerNetData);
+ /*
+ * If bytesRead are 0 put we still have some data in peerNetData still to an unwrap (for testcase 1.1.6)
+ */
+ if (bytesRead > 0 || peerNetData.hasRemaining()) {
+ peerNetData.flip();
+ while (peerNetData.hasRemaining()) {
+ peerAppData.compact();
+ SSLEngineResult result;
+ try {
+ result = engine.unwrap(peerNetData, peerAppData);
+ } catch (SSLException e) {
+ log.error("SSLException during unwrap", e);
+ throw e;
+ }
+ switch (result.getStatus()) {
+ case OK:
+ peerAppData.flip();
+ return ByteBufferUtils.transferByteBuffer(peerAppData, dst);
+ case BUFFER_UNDERFLOW:
+ peerAppData.flip();
+ return ByteBufferUtils.transferByteBuffer(peerAppData, dst);
+ case BUFFER_OVERFLOW:
+ peerAppData = enlargeApplicationBuffer(peerAppData);
+ return read(dst);
+ case CLOSED:
+ closeConnection();
+ dst.clear();
+ return -1;
+ default:
+ throw new IllegalStateException("Invalid SSL status: " + result.getStatus());
+ }
+ }
+ } else if (bytesRead < 0) {
+ handleEndOfStream();
+ }
+ ByteBufferUtils.transferByteBuffer(peerAppData, dst);
+ return bytesRead;
+ }
+
+ @Override
+ public synchronized int write(ByteBuffer output) throws IOException {
+ int num = 0;
+ while (output.hasRemaining()) {
+ // The loop has a meaning for (outgoing) messages larger than 16KB.
+ // Every wrap call will remove 16KB from the original message and send it to the remote peer.
+ myNetData.clear();
+ SSLEngineResult result = engine.wrap(output, myNetData);
+ switch (result.getStatus()) {
+ case OK:
+ myNetData.flip();
+ while (myNetData.hasRemaining()) {
+ num += socketChannel.write(myNetData);
+ }
+ break;
+ case BUFFER_OVERFLOW:
+ myNetData = enlargePacketBuffer(myNetData);
+ break;
+ case BUFFER_UNDERFLOW:
+ throw new SSLException(
+ "Buffer underflow occurred after a wrap. I don't think we should ever get here.");
+ case CLOSED:
+ closeConnection();
+ return 0;
+ default:
+ throw new IllegalStateException("Invalid SSL status: " + result.getStatus());
+ }
+ }
+ return num;
+ }
+
+ /**
+ * Implements the handshake protocol between two peers, required for the establishment of the
+ * SSL/TLS connection. During the handshake, encryption configuration information - such as the
+ * list of available cipher suites - will be exchanged and if the handshake is successful will
+ * lead to an established SSL/TLS session.
+ *
+ *
+ * A typical handshake will usually contain the following steps:
+ *
+ *
+ * - 1. wrap: ClientHello
+ * - 2. unwrap: ServerHello/Cert/ServerHelloDone
+ * - 3. wrap: ClientKeyExchange
+ * - 4. wrap: ChangeCipherSpec
+ * - 5. wrap: Finished
+ * - 6. unwrap: ChangeCipherSpec
+ * - 7. unwrap: Finished
+ *
+ *
+ * Handshake is also used during the end of the session, in order to properly close the connection between the two peers.
+ * A proper connection close will typically include the one peer sending a CLOSE message to another, and then wait for
+ * the other's CLOSE message to close the transport link. The other peer from his perspective would read a CLOSE message
+ * from his peer and then enter the handshake procedure to send his own CLOSE message as well.
+ *
+ * @return True if the connection handshake was successful or false if an error occurred.
+ * @throws IOException - if an error occurs during read/write to the socket channel.
+ */
+ private boolean doHandshake() throws IOException {
+ SSLEngineResult result;
+ HandshakeStatus handshakeStatus;
+
+ // NioSslPeer's fields myAppData and peerAppData are supposed to be large enough to hold all message data the peer
+ // will send and expects to receive from the other peer respectively. Since the messages to be exchanged will usually be less
+ // than 16KB long the capacity of these fields should also be smaller. Here we initialize these two local buffers
+ // to be used for the handshake, while keeping client's buffers at the same size.
+ int appBufferSize = engine.getSession().getApplicationBufferSize();
+ myAppData = ByteBuffer.allocate(appBufferSize);
+ peerAppData = ByteBuffer.allocate(appBufferSize);
+ myNetData.clear();
+ peerNetData.clear();
+
+ handshakeStatus = engine.getHandshakeStatus();
+ boolean handshakeComplete = false;
+ while (!handshakeComplete) {
+ switch (handshakeStatus) {
+ case FINISHED:
+ handshakeComplete = !this.peerNetData.hasRemaining();
+ if (handshakeComplete) {
+ return true;
+ }
+ socketChannel.write(this.peerNetData);
+ break;
+ case NEED_UNWRAP:
+ if (socketChannel.read(peerNetData) < 0) {
+ if (engine.isInboundDone() && engine.isOutboundDone()) {
+ return false;
+ }
+ try {
+ engine.closeInbound();
+ } catch (SSLException e) {
+ //Ignore, can't do anything against this exception
+ }
+ engine.closeOutbound();
+ // After closeOutbound the engine will be set to WRAP state, in order to try to send a close message to the client.
+ handshakeStatus = engine.getHandshakeStatus();
+ break;
+ }
+ peerNetData.flip();
+ try {
+ result = engine.unwrap(peerNetData, peerAppData);
+ peerNetData.compact();
+ handshakeStatus = result.getHandshakeStatus();
+ } catch (SSLException sslException) {
+ engine.closeOutbound();
+ handshakeStatus = engine.getHandshakeStatus();
+ break;
+ }
+ switch (result.getStatus()) {
+ case OK:
+ break;
+ case BUFFER_OVERFLOW:
+ // Will occur when peerAppData's capacity is smaller than the data derived from peerNetData's unwrap.
+ peerAppData = enlargeApplicationBuffer(peerAppData);
+ break;
+ case BUFFER_UNDERFLOW:
+ // Will occur either when no data was read from the peer or when the peerNetData buffer was too small to hold all peer's data.
+ peerNetData = handleBufferUnderflow(peerNetData);
+ break;
+ case CLOSED:
+ if (engine.isOutboundDone()) {
+ return false;
+ } else {
+ engine.closeOutbound();
+ handshakeStatus = engine.getHandshakeStatus();
+ break;
+ }
+ default:
+ throw new IllegalStateException("Invalid SSL status: " + result.getStatus());
+ }
+ break;
+ case NEED_WRAP:
+ myNetData.clear();
+ try {
+ result = engine.wrap(myAppData, myNetData);
+ handshakeStatus = result.getHandshakeStatus();
+ } catch (SSLException sslException) {
+ engine.closeOutbound();
+ handshakeStatus = engine.getHandshakeStatus();
+ break;
+ }
+ switch (result.getStatus()) {
+ case OK:
+ myNetData.flip();
+ while (myNetData.hasRemaining()) {
+ socketChannel.write(myNetData);
+ }
+ break;
+ case BUFFER_OVERFLOW:
+ // Will occur if there is not enough space in myNetData buffer to write all the data that would be generated by the method wrap.
+ // Since myNetData is set to session's packet size we should not get to this point because SSLEngine is supposed
+ // to produce messages smaller or equal to that, but a general handling would be the following:
+ myNetData = enlargePacketBuffer(myNetData);
+ break;
+ case BUFFER_UNDERFLOW:
+ throw new SSLException(
+ "Buffer underflow occurred after a wrap. I don't think we should ever get here.");
+ case CLOSED:
+ try {
+ myNetData.flip();
+ while (myNetData.hasRemaining()) {
+ socketChannel.write(myNetData);
+ }
+ // At this point the handshake status will probably be NEED_UNWRAP so we make sure that peerNetData is clear to read.
+ peerNetData.clear();
+ } catch (Exception e) {
+ handshakeStatus = engine.getHandshakeStatus();
+ }
+ break;
+ default:
+ throw new IllegalStateException("Invalid SSL status: " + result.getStatus());
+ }
+ break;
+ case NEED_TASK:
+ Runnable task;
+ while ((task = engine.getDelegatedTask()) != null) {
+ executor.execute(task);
+ }
+ handshakeStatus = engine.getHandshakeStatus();
+ break;
+
+ case NOT_HANDSHAKING:
+ break;
+ default:
+ throw new IllegalStateException("Invalid SSL status: " + handshakeStatus);
+ }
+ }
+
+ return true;
+
+ }
+
+ /**
+ * Enlarging a packet buffer (peerNetData or myNetData)
+ *
+ * @param buffer the buffer to enlarge
+ * @return the enlarged buffer
+ */
+ private ByteBuffer enlargePacketBuffer(ByteBuffer buffer) {
+ return enlargeBuffer(buffer, engine.getSession().getPacketBufferSize());
+ }
+
+ /**
+ * Enlarging a packet buffer (peerAppData or myAppData)
+ *
+ * @param buffer the buffer to enlarge
+ * @return the enlarged buffer
+ */
+ private ByteBuffer enlargeApplicationBuffer(ByteBuffer buffer) {
+ return enlargeBuffer(buffer, engine.getSession().getApplicationBufferSize());
+ }
+
+ /**
+ * Compares sessionProposedCapacity with buffer's capacity. If buffer's capacity is
+ * smaller, returns a buffer with the proposed capacity. If it's equal or larger, returns a buffer
+ * with capacity twice the size of the initial one.
+ *
+ * @param buffer - the buffer to be enlarged.
+ * @param sessionProposedCapacity - the minimum size of the new buffer, proposed by {@link
+ * SSLSession}.
+ * @return A new buffer with a larger capacity.
+ */
+ private ByteBuffer enlargeBuffer(ByteBuffer buffer, int sessionProposedCapacity) {
+ if (sessionProposedCapacity > buffer.capacity()) {
+ buffer = ByteBuffer.allocate(sessionProposedCapacity);
+ } else {
+ buffer = ByteBuffer.allocate(buffer.capacity() * 2);
+ }
+ return buffer;
+ }
+
+ /**
+ * Handles {@link SSLEngineResult.Status#BUFFER_UNDERFLOW}. Will check if the buffer is already
+ * filled, and if there is no space problem will return the same buffer, so the client tries to
+ * read again. If the buffer is already filled will try to enlarge the buffer either to session's
+ * proposed size or to a larger capacity. A buffer underflow can happen only after an unwrap, so
+ * the buffer will always be a peerNetData buffer.
+ *
+ * @param buffer - will always be peerNetData buffer.
+ * @return The same buffer if there is no space problem or a new buffer with the same data but
+ * more space.
+ */
+ private ByteBuffer handleBufferUnderflow(ByteBuffer buffer) {
+ if (engine.getSession().getPacketBufferSize() < buffer.limit()) {
+ return buffer;
+ } else {
+ ByteBuffer replaceBuffer = enlargePacketBuffer(buffer);
+ buffer.flip();
+ replaceBuffer.put(buffer);
+ return replaceBuffer;
+ }
+ }
+
+ /**
+ * This method should be called when this peer wants to explicitly close the connection or when a
+ * close message has arrived from the other peer, in order to provide an orderly shutdown.
+ *
+ * It first calls {@link SSLEngine#closeOutbound()} which prepares this peer to send its own close
+ * message and sets {@link SSLEngine} to the NEED_WRAP state. Then, it delegates the
+ * exchange of close messages to the handshake method and finally, it closes socket channel.
+ *
+ * @throws IOException if an I/O error occurs to the socket channel.
+ */
+ private void closeConnection() throws IOException {
+ engine.closeOutbound();
+ try {
+ doHandshake();
+ } catch (IOException e) {
+ //Just ignore this exception since we are closing the connection already
+ }
+ socketChannel.close();
+ }
+
+ /**
+ * In addition to orderly shutdowns, an unorderly shutdown may occur, when the transport link
+ * (socket channel) is severed before close messages are exchanged. This may happen by getting an
+ * -1 or {@link IOException} when trying to read from the socket channel, or an {@link
+ * IOException} when trying to write to it. In both cases {@link SSLEngine#closeInbound()} should
+ * be called and then try to follow the standard procedure.
+ *
+ * @throws IOException if an I/O error occurs to the socket channel.
+ */
+ private void handleEndOfStream() throws IOException {
+ try {
+ engine.closeInbound();
+ } catch (Exception e) {
+ log.error(
+ "This engine was forced to close inbound, without having received the proper SSL/TLS close notification message from the peer, due to end of stream.");
+ }
+ closeConnection();
+ }
+
+ @Override
+ public boolean isNeedWrite() {
+ return false;
+ }
+
+ @Override
+ public void writeMore() throws IOException {
+ //Nothing to do since we write out all the data in a while loop
+ }
+
+ @Override
+ public boolean isNeedRead() {
+ return peerNetData.hasRemaining() || peerAppData.hasRemaining();
+ }
+
+ @Override
+ public int readMore(ByteBuffer dst) throws IOException {
+ return read(dst);
+ }
+
+ @Override
+ public boolean isBlocking() {
+ return socketChannel.isBlocking();
+ }
+
+
+ @Override
+ public boolean isOpen() {
+ return socketChannel.isOpen();
+ }
+
+ @Override
+ public void close() throws IOException {
+ closeConnection();
+ }
+
+ @Override
+ public SSLEngine getSSLEngine() {
+ return engine;
+ }
}
\ No newline at end of file
diff --git a/src/main/java/org/java_websocket/SSLSocketChannel2.java b/src/main/java/org/java_websocket/SSLSocketChannel2.java
index 3191edd90..23c4f8af1 100644
--- a/src/main/java/org/java_websocket/SSLSocketChannel2.java
+++ b/src/main/java/org/java_websocket/SSLSocketChannel2.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -22,14 +22,9 @@
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
+
package org.java_websocket;
-import javax.net.ssl.SSLEngine;
-import javax.net.ssl.SSLEngineResult;
-import javax.net.ssl.SSLEngineResult.HandshakeStatus;
-import javax.net.ssl.SSLEngineResult.Status;
-import javax.net.ssl.SSLException;
-import javax.net.ssl.SSLSession;
import java.io.EOFException;
import java.io.IOException;
import java.net.Socket;
@@ -45,371 +40,462 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLEngineResult.HandshakeStatus;
+import javax.net.ssl.SSLEngineResult.Status;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLSession;
+import org.java_websocket.interfaces.ISSLChannel;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* Implements the relevant portions of the SocketChannel interface with the SSLEngine wrapper.
*/
-public class SSLSocketChannel2 implements ByteChannel, WrappedByteChannel {
+public class SSLSocketChannel2 implements ByteChannel, WrappedByteChannel, ISSLChannel {
/**
- * This object is used to feed the {@link SSLEngine}'s wrap and unwrap methods during the handshake phase.
- **/
- protected static ByteBuffer emptybuffer = ByteBuffer.allocate( 0 );
-
- protected ExecutorService exec;
-
- protected List> tasks;
-
- /** raw payload incomming */
- protected ByteBuffer inData;
- /** encrypted data outgoing */
- protected ByteBuffer outCrypt;
- /** encrypted data incoming */
- protected ByteBuffer inCrypt;
-
- /** the underlying channel */
- protected SocketChannel socketChannel;
- /** used to set interestOP SelectionKey.OP_WRITE for the underlying channel */
- protected SelectionKey selectionKey;
-
- protected SSLEngine sslEngine;
- protected SSLEngineResult readEngineResult;
- protected SSLEngineResult writeEngineResult;
-
- /**
- * Should be used to count the buffer allocations.
- * But because of #190 where HandshakeStatus.FINISHED is not properly returned by nio wrap/unwrap this variable is used to check whether {@link #createBuffers(SSLSession)} needs to be called.
- **/
- protected int bufferallocations = 0;
-
- public SSLSocketChannel2( SocketChannel channel , SSLEngine sslEngine , ExecutorService exec , SelectionKey key ) throws IOException {
- if( channel == null || sslEngine == null || exec == null )
- throw new IllegalArgumentException( "parameter must not be null" );
-
- this.socketChannel = channel;
- this.sslEngine = sslEngine;
- this.exec = exec;
-
- readEngineResult = writeEngineResult = new SSLEngineResult( Status.BUFFER_UNDERFLOW, sslEngine.getHandshakeStatus(), 0, 0 ); // init to prevent NPEs
-
- tasks = new ArrayList>( 3 );
- if( key != null ) {
- key.interestOps( key.interestOps() | SelectionKey.OP_WRITE );
- this.selectionKey = key;
- }
- createBuffers( sslEngine.getSession() );
- // kick off handshake
- socketChannel.write( wrap( emptybuffer ) );// initializes res
- processHandshake();
- }
+ * This object is used to feed the {@link SSLEngine}'s wrap and unwrap methods during the
+ * handshake phase.
+ **/
+ protected static ByteBuffer emptybuffer = ByteBuffer.allocate(0);
- private void consumeFutureUninterruptible( Future> f ) {
- try {
- boolean interrupted = false;
- while ( true ) {
- try {
- f.get();
- break;
- } catch ( InterruptedException e ) {
- interrupted = true;
- }
- }
- if( interrupted )
- Thread.currentThread().interrupt();
- } catch ( ExecutionException e ) {
- throw new RuntimeException( e );
- }
- }
+ /**
+ * Logger instance
+ *
+ * @since 1.4.0
+ */
+ private final Logger log = LoggerFactory.getLogger(SSLSocketChannel2.class);
- /**
- * This method will do whatever necessary to process the sslengine handshake.
- * Thats why it's called both from the {@link #read(ByteBuffer)} and {@link #write(ByteBuffer)}
- **/
- private synchronized void processHandshake() throws IOException {
- if( sslEngine.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING )
- return; // since this may be called either from a reading or a writing thread and because this method is synchronized it is necessary to double check if we are still handshaking.
- if( !tasks.isEmpty() ) {
- Iterator> it = tasks.iterator();
- while ( it.hasNext() ) {
- Future> f = it.next();
- if( f.isDone() ) {
- it.remove();
- } else {
- if( isBlocking() )
- consumeFutureUninterruptible( f );
- return;
- }
- }
- }
+ protected ExecutorService exec;
- if( sslEngine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP ) {
- if( !isBlocking() || readEngineResult.getStatus() == Status.BUFFER_UNDERFLOW ) {
- inCrypt.compact();
- int read = socketChannel.read( inCrypt );
- if( read == -1 ) {
- throw new IOException( "connection closed unexpectedly by peer" );
- }
- inCrypt.flip();
- }
- inData.compact();
- unwrap();
- if( readEngineResult.getHandshakeStatus() == HandshakeStatus.FINISHED ) {
- createBuffers( sslEngine.getSession() );
- return;
- }
- }
- consumeDelegatedTasks();
- if( tasks.isEmpty() || sslEngine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_WRAP ) {
- socketChannel.write( wrap( emptybuffer ) );
- if( writeEngineResult.getHandshakeStatus() == HandshakeStatus.FINISHED ) {
- createBuffers( sslEngine.getSession() );
- return;
- }
- }
- assert ( sslEngine.getHandshakeStatus() != HandshakeStatus.NOT_HANDSHAKING );// this function could only leave NOT_HANDSHAKING after createBuffers was called unless #190 occurs which means that nio wrap/unwrap never return HandshakeStatus.FINISHED
+ protected List> tasks;
- bufferallocations = 1; // look at variable declaration why this line exists and #190. Without this line buffers would not be be recreated when #190 AND a rehandshake occur.
- }
- private synchronized ByteBuffer wrap( ByteBuffer b ) throws SSLException {
- outCrypt.compact();
- writeEngineResult = sslEngine.wrap( b, outCrypt );
- outCrypt.flip();
- return outCrypt;
+ /**
+ * raw payload incoming
+ */
+ protected ByteBuffer inData;
+ /**
+ * encrypted data outgoing
+ */
+ protected ByteBuffer outCrypt;
+ /**
+ * encrypted data incoming
+ */
+ protected ByteBuffer inCrypt;
+
+ /**
+ * the underlying channel
+ */
+ protected SocketChannel socketChannel;
+ /**
+ * used to set interestOP SelectionKey.OP_WRITE for the underlying channel
+ */
+ protected SelectionKey selectionKey;
+
+ protected SSLEngine sslEngine;
+ protected SSLEngineResult readEngineResult;
+ protected SSLEngineResult writeEngineResult;
+
+ /**
+ * Should be used to count the buffer allocations. But because of #190 where
+ * HandshakeStatus.FINISHED is not properly returned by nio wrap/unwrap this variable is used to
+ * check whether {@link #createBuffers(SSLSession)} needs to be called.
+ **/
+ protected int bufferallocations = 0;
+
+ public SSLSocketChannel2(SocketChannel channel, SSLEngine sslEngine, ExecutorService exec,
+ SelectionKey key) throws IOException {
+ if (channel == null || sslEngine == null || exec == null) {
+ throw new IllegalArgumentException("parameter must not be null");
}
- /**
- * performs the unwrap operation by unwrapping from {@link #inCrypt} to {@link #inData}
- **/
- private synchronized ByteBuffer unwrap() throws SSLException {
- int rem;
- //There are some ssl test suites, which get around the selector.select() call, which cause an infinite unwrap and 100% cpu usage (see #459 and #458)
- if(readEngineResult.getStatus() == SSLEngineResult.Status.CLOSED && sslEngine.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING){
- try {
- close();
- } catch (IOException e) {
- //Not really interesting
- }
+ this.socketChannel = channel;
+ this.sslEngine = sslEngine;
+ this.exec = exec;
+
+ readEngineResult = writeEngineResult = new SSLEngineResult(Status.BUFFER_UNDERFLOW,
+ sslEngine.getHandshakeStatus(), 0, 0); // init to prevent NPEs
+
+ tasks = new ArrayList>(3);
+ if (key != null) {
+ key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);
+ this.selectionKey = key;
+ }
+ createBuffers(sslEngine.getSession());
+ // kick off handshake
+ socketChannel.write(wrap(emptybuffer));// initializes res
+ processHandshake(false);
+ }
+
+ private void consumeFutureUninterruptible(Future> f) {
+ try {
+ while (true) {
+ try {
+ f.get();
+ break;
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
}
- do {
- rem = inData.remaining();
- readEngineResult = sslEngine.unwrap( inCrypt, inData );
- } while ( readEngineResult.getStatus() == SSLEngineResult.Status.OK && ( rem != inData.remaining() || sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP ) );
- inData.flip();
- return inData;
+ }
+ } catch (ExecutionException e) {
+ throw new RuntimeException(e);
}
+ }
- protected void consumeDelegatedTasks() {
- Runnable task;
- while ( ( task = sslEngine.getDelegatedTask() ) != null ) {
- tasks.add( exec.submit( task ) );
- // task.run();
+ /**
+ * This method will do whatever necessary to process the sslEngine handshake. Thats why it's
+ * called both from the {@link #read(ByteBuffer)} and {@link #write(ByteBuffer)}
+ **/
+ private synchronized void processHandshake(boolean isReading) throws IOException {
+ if (sslEngine.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING) {
+ return; // since this may be called either from a reading or a writing thread and because this method is synchronized it is necessary to double check if we are still handshaking.
+ }
+ if (!tasks.isEmpty()) {
+ Iterator> it = tasks.iterator();
+ while (it.hasNext()) {
+ Future> f = it.next();
+ if (f.isDone()) {
+ it.remove();
+ } else {
+ if (isBlocking()) {
+ consumeFutureUninterruptible(f);
+ }
+ return;
}
+ }
}
- protected void createBuffers( SSLSession session ) {
- int netBufferMax = session.getPacketBufferSize();
- int appBufferMax = Math.max(session.getApplicationBufferSize(), netBufferMax);
-
- if( inData == null ) {
- inData = ByteBuffer.allocate( appBufferMax );
- outCrypt = ByteBuffer.allocate( netBufferMax );
- inCrypt = ByteBuffer.allocate( netBufferMax );
- } else {
- if( inData.capacity() != appBufferMax )
- inData = ByteBuffer.allocate( appBufferMax );
- if( outCrypt.capacity() != netBufferMax )
- outCrypt = ByteBuffer.allocate( netBufferMax );
- if( inCrypt.capacity() != netBufferMax )
- inCrypt = ByteBuffer.allocate( netBufferMax );
+ if (isReading && sslEngine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP) {
+ if (!isBlocking() || readEngineResult.getStatus() == Status.BUFFER_UNDERFLOW) {
+ inCrypt.compact();
+ int read = socketChannel.read(inCrypt);
+ if (read == -1) {
+ throw new IOException("connection closed unexpectedly by peer");
}
- inData.rewind();
- inData.flip();
- inCrypt.rewind();
inCrypt.flip();
- outCrypt.rewind();
- outCrypt.flip();
- bufferallocations++;
+ }
+ inData.compact();
+ unwrap();
+ if (readEngineResult.getHandshakeStatus() == HandshakeStatus.FINISHED) {
+ createBuffers(sslEngine.getSession());
+ return;
+ }
}
+ consumeDelegatedTasks();
+ if (tasks.isEmpty()
+ || sslEngine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_WRAP) {
+ socketChannel.write(wrap(emptybuffer));
+ if (writeEngineResult.getHandshakeStatus() == HandshakeStatus.FINISHED) {
+ createBuffers(sslEngine.getSession());
+ return;
+ }
+ }
+ assert (sslEngine.getHandshakeStatus()
+ != HandshakeStatus.NOT_HANDSHAKING);// this function could only leave NOT_HANDSHAKING after createBuffers was called unless #190 occurs which means that nio wrap/unwrap never return HandshakeStatus.FINISHED
- public int write( ByteBuffer src ) throws IOException {
- if( !isHandShakeComplete() ) {
- processHandshake();
- return 0;
- }
- // assert ( bufferallocations > 1 ); //see #190
- //if( bufferallocations <= 1 ) {
- // createBuffers( sslEngine.getSession() );
- //}
- int num = socketChannel.write( wrap( src ) );
- if (writeEngineResult.getStatus() == SSLEngineResult.Status.CLOSED) {
- throw new EOFException("Connection is closed");
- }
- return num;
+ bufferallocations = 1; // look at variable declaration why this line exists and #190. Without this line buffers would not be be recreated when #190 AND a rehandshake occur.
+ }
- }
+ private synchronized ByteBuffer wrap(ByteBuffer b) throws SSLException {
+ outCrypt.compact();
+ writeEngineResult = sslEngine.wrap(b, outCrypt);
+ outCrypt.flip();
+ return outCrypt;
+ }
- /**
- * Blocks when in blocking mode until at least one byte has been decoded.
- * When not in blocking mode 0 may be returned.
- *
- * @return the number of bytes read.
- **/
- public int read(ByteBuffer dst) throws IOException {
- while (true) {
- if (!dst.hasRemaining())
- return 0;
- if (!isHandShakeComplete()) {
- if (isBlocking()) {
- while (!isHandShakeComplete()) {
- processHandshake();
- }
- } else {
- processHandshake();
- if (!isHandShakeComplete()) {
- return 0;
- }
- }
- }
- // assert ( bufferallocations > 1 ); //see #190
- //if( bufferallocations <= 1 ) {
- // createBuffers( sslEngine.getSession() );
- //}
- /* 1. When "dst" is smaller than "inData" readRemaining will fill "dst" with data decoded in a previous read call.
- * 2. When "inCrypt" contains more data than "inData" has remaining space, unwrap has to be called on more time(readRemaining)
- */
- int purged = readRemaining(dst);
- if (purged != 0)
- return purged;
-
- /* We only continue when we really need more data from the network.
- * Thats the case if inData is empty or inCrypt holds to less data than necessary for decryption
- */
- assert (inData.position() == 0);
- inData.clear();
-
- if (!inCrypt.hasRemaining())
- inCrypt.clear();
- else
- inCrypt.compact();
-
- if (isBlocking() || readEngineResult.getStatus() == Status.BUFFER_UNDERFLOW)
- if (socketChannel.read(inCrypt) == -1) {
- return -1;
- }
- inCrypt.flip();
- unwrap();
-
- int transfered = transfereTo(inData, dst);
- if (transfered == 0 && isBlocking()) {
- continue;
- }
- return transfered;
- }
+ /**
+ * performs the unwrap operation by unwrapping from {@link #inCrypt} to {@link #inData}
+ **/
+ private synchronized ByteBuffer unwrap() throws SSLException {
+ int rem;
+ //There are some ssl test suites, which get around the selector.select() call, which cause an infinite unwrap and 100% cpu usage (see #459 and #458)
+ if (readEngineResult.getStatus() == SSLEngineResult.Status.CLOSED
+ && sslEngine.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING) {
+ try {
+ close();
+ } catch (IOException e) {
+ //Not really interesting
+ }
}
- /**
- * {@link #read(ByteBuffer)} may not be to leave all buffers(inData, inCrypt)
- **/
- private int readRemaining( ByteBuffer dst ) throws SSLException {
- if( inData.hasRemaining() ) {
- return transfereTo( inData, dst );
- }
- if( !inData.hasRemaining() )
- inData.clear();
- // test if some bytes left from last read (e.g. BUFFER_UNDERFLOW)
- if( inCrypt.hasRemaining() ) {
- unwrap();
- int amount = transfereTo( inData, dst );
- if (readEngineResult.getStatus() == SSLEngineResult.Status.CLOSED) {
- return -1;
- }
- if( amount > 0 )
- return amount;
- }
- return 0;
+ do {
+ rem = inData.remaining();
+ readEngineResult = sslEngine.unwrap(inCrypt, inData);
+ } while (readEngineResult.getStatus() == SSLEngineResult.Status.OK && (rem != inData.remaining()
+ || sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP));
+ inData.flip();
+ return inData;
+ }
+
+ protected void consumeDelegatedTasks() {
+ Runnable task;
+ while ((task = sslEngine.getDelegatedTask()) != null) {
+ tasks.add(exec.submit(task));
+ // task.run();
}
-
- public boolean isConnected() {
- return socketChannel.isConnected();
+ }
+
+ protected void createBuffers(SSLSession session) {
+ saveCryptedData(); // save any remaining data in inCrypt
+ int netBufferMax = session.getPacketBufferSize();
+ int appBufferMax = Math.max(session.getApplicationBufferSize(), netBufferMax);
+
+ if (inData == null) {
+ inData = ByteBuffer.allocate(appBufferMax);
+ outCrypt = ByteBuffer.allocate(netBufferMax);
+ inCrypt = ByteBuffer.allocate(netBufferMax);
+ } else {
+ if (inData.capacity() != appBufferMax) {
+ inData = ByteBuffer.allocate(appBufferMax);
+ }
+ if (outCrypt.capacity() != netBufferMax) {
+ outCrypt = ByteBuffer.allocate(netBufferMax);
+ }
+ if (inCrypt.capacity() != netBufferMax) {
+ inCrypt = ByteBuffer.allocate(netBufferMax);
+ }
}
-
- public void close() throws IOException {
- sslEngine.closeOutbound();
- sslEngine.getSession().invalidate();
- if( socketChannel.isOpen() )
- socketChannel.write( wrap( emptybuffer ) );// FIXME what if not all bytes can be written
- socketChannel.close();
+ if (inData.remaining() != 0 && log.isTraceEnabled()) {
+ log.trace(new String(inData.array(), inData.position(), inData.remaining()));
}
-
- private boolean isHandShakeComplete() {
- HandshakeStatus status = sslEngine.getHandshakeStatus();
- return status == SSLEngineResult.HandshakeStatus.FINISHED || status == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING;
+ inData.rewind();
+ inData.flip();
+ if (inCrypt.remaining() != 0 && log.isTraceEnabled()) {
+ log.trace(new String(inCrypt.array(), inCrypt.position(), inCrypt.remaining()));
}
-
- public SelectableChannel configureBlocking( boolean b ) throws IOException {
- return socketChannel.configureBlocking( b );
+ inCrypt.rewind();
+ inCrypt.flip();
+ outCrypt.rewind();
+ outCrypt.flip();
+ bufferallocations++;
+ }
+
+ public int write(ByteBuffer src) throws IOException {
+ if (!isHandShakeComplete()) {
+ processHandshake(false);
+ return 0;
}
-
- public boolean connect( SocketAddress remote ) throws IOException {
- return socketChannel.connect( remote );
+ // assert(bufferallocations > 1); // see #190
+ // if(bufferallocations <= 1) {
+ // createBuffers(sslEngine.getSession());
+ // }
+ int num = socketChannel.write(wrap(src));
+ if (writeEngineResult.getStatus() == SSLEngineResult.Status.CLOSED) {
+ throw new EOFException("Connection is closed");
}
+ return num;
- public boolean finishConnect() throws IOException {
- return socketChannel.finishConnect();
- }
+ }
- public Socket socket() {
- return socketChannel.socket();
+ /**
+ * Blocks when in blocking mode until at least one byte has been decoded.
When not in blocking
+ * mode 0 may be returned.
+ *
+ * @return the number of bytes read.
+ **/
+ public int read(ByteBuffer dst) throws IOException {
+ tryRestoreCryptedData();
+ while (true) {
+ if (!dst.hasRemaining()) {
+ return 0;
+ }
+ if (!isHandShakeComplete()) {
+ if (isBlocking()) {
+ while (!isHandShakeComplete()) {
+ processHandshake(true);
+ }
+ } else {
+ processHandshake(true);
+ if (!isHandShakeComplete()) {
+ return 0;
+ }
+ }
+ }
+ // assert(bufferallocations > 1); // see #190
+ // if (bufferallocations <= 1) {
+ // createBuffers(sslEngine.getSession());
+ // }
+
+ /* 1. When "dst" is smaller than "inData" readRemaining will fill "dst" with data decoded in a previous read call.
+ * 2. When "inCrypt" contains more data than "inData" has remaining space, unwrap has to be called on more time(readRemaining)
+ */
+ int purged = readRemaining(dst);
+ if (purged != 0) {
+ return purged;
+ }
+
+ /* We only continue when we really need more data from the network.
+ * Thats the case if inData is empty or inCrypt holds to less data than necessary for decryption
+ */
+ assert (inData.position() == 0);
+ inData.clear();
+
+ if (!inCrypt.hasRemaining()) {
+ inCrypt.clear();
+ } else {
+ inCrypt.compact();
+ }
+
+ if (isBlocking() || readEngineResult.getStatus() == Status.BUFFER_UNDERFLOW) {
+ if (socketChannel.read(inCrypt) == -1) {
+ return -1;
+ }
+ }
+ inCrypt.flip();
+ unwrap();
+
+ int transferred = transfereTo(inData, dst);
+ if (transferred == 0 && isBlocking()) {
+ continue;
+ }
+ return transferred;
}
+ }
- public boolean isInboundDone() {
- return sslEngine.isInboundDone();
+ /**
+ * {@link #read(ByteBuffer)} may not be to leave all buffers(inData, inCrypt)
+ **/
+ private int readRemaining(ByteBuffer dst) throws SSLException {
+ if (inData.hasRemaining()) {
+ return transfereTo(inData, dst);
}
-
- @Override
- public boolean isOpen() {
- return socketChannel.isOpen();
+ if (!inData.hasRemaining()) {
+ inData.clear();
}
-
- @Override
- public boolean isNeedWrite() {
- return outCrypt.hasRemaining() || !isHandShakeComplete(); // FIXME this condition can cause high cpu load during handshaking when network is slow
+ tryRestoreCryptedData();
+ // test if some bytes left from last read (e.g. BUFFER_UNDERFLOW)
+ if (inCrypt.hasRemaining()) {
+ unwrap();
+ int amount = transfereTo(inData, dst);
+ if (readEngineResult.getStatus() == SSLEngineResult.Status.CLOSED) {
+ return -1;
+ }
+ if (amount > 0) {
+ return amount;
+ }
}
-
- @Override
- public void writeMore() throws IOException {
- write( outCrypt );
+ return 0;
+ }
+
+ public boolean isConnected() {
+ return socketChannel.isConnected();
+ }
+
+ public void close() throws IOException {
+ sslEngine.closeOutbound();
+ sslEngine.getSession().invalidate();
+ try {
+ if (socketChannel.isOpen()) {
+ socketChannel.write(wrap(emptybuffer));
+ }
+ } finally { // in case socketChannel.write produce exception - channel will never close
+ socketChannel.close();
}
-
- @Override
- public boolean isNeedRead() {
- return inData.hasRemaining() || ( inCrypt.hasRemaining() && readEngineResult.getStatus() != Status.BUFFER_UNDERFLOW && readEngineResult.getStatus() != Status.CLOSED );
+ }
+
+ private boolean isHandShakeComplete() {
+ HandshakeStatus status = sslEngine.getHandshakeStatus();
+ return status == SSLEngineResult.HandshakeStatus.FINISHED
+ || status == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING;
+ }
+
+ public SelectableChannel configureBlocking(boolean b) throws IOException {
+ return socketChannel.configureBlocking(b);
+ }
+
+ public boolean connect(SocketAddress remote) throws IOException {
+ return socketChannel.connect(remote);
+ }
+
+ public boolean finishConnect() throws IOException {
+ return socketChannel.finishConnect();
+ }
+
+ public Socket socket() {
+ return socketChannel.socket();
+ }
+
+ public boolean isInboundDone() {
+ return sslEngine.isInboundDone();
+ }
+
+ @Override
+ public boolean isOpen() {
+ return socketChannel.isOpen();
+ }
+
+ @Override
+ public boolean isNeedWrite() {
+ return outCrypt.hasRemaining()
+ || !isHandShakeComplete(); // FIXME this condition can cause high cpu load during handshaking when network is slow
+ }
+
+ @Override
+ public void writeMore() throws IOException {
+ write(outCrypt);
+ }
+
+ @Override
+ public boolean isNeedRead() {
+ return saveCryptData != null || inData.hasRemaining() || (inCrypt.hasRemaining()
+ && readEngineResult.getStatus() != Status.BUFFER_UNDERFLOW
+ && readEngineResult.getStatus() != Status.CLOSED);
+ }
+
+ @Override
+ public int readMore(ByteBuffer dst) throws SSLException {
+ return readRemaining(dst);
+ }
+
+ private int transfereTo(ByteBuffer from, ByteBuffer to) {
+ int fremain = from.remaining();
+ int toremain = to.remaining();
+ if (fremain > toremain) {
+ // FIXME there should be a more efficient transfer method
+ int limit = Math.min(fremain, toremain);
+ for (int i = 0; i < limit; i++) {
+ to.put(from.get());
+ }
+ return limit;
+ } else {
+ to.put(from);
+ return fremain;
}
- @Override
- public int readMore( ByteBuffer dst ) throws SSLException {
- return readRemaining( dst );
- }
+ }
- private int transfereTo( ByteBuffer from, ByteBuffer to ) {
- int fremain = from.remaining();
- int toremain = to.remaining();
- if( fremain > toremain ) {
- // FIXME there should be a more efficient transfer method
- int limit = Math.min( fremain, toremain );
- for( int i = 0 ; i < limit ; i++ ) {
- to.put( from.get() );
- }
- return limit;
- } else {
- to.put( from );
- return fremain;
- }
+ @Override
+ public boolean isBlocking() {
+ return socketChannel.isBlocking();
+ }
- }
+ @Override
+ public SSLEngine getSSLEngine() {
+ return sslEngine;
+ }
- @Override
- public boolean isBlocking() {
- return socketChannel.isBlocking();
- }
+ // to avoid complexities with inCrypt, extra unwrapped data after SSL handshake will be saved off in a byte array
+ // and the inserted back on first read
+ private byte[] saveCryptData = null;
+
+ private void saveCryptedData() {
+ // did we find any extra data?
+ if (inCrypt != null && inCrypt.remaining() > 0) {
+ int saveCryptSize = inCrypt.remaining();
+ saveCryptData = new byte[saveCryptSize];
+ inCrypt.get(saveCryptData);
+ }
+ }
+
+ private void tryRestoreCryptedData() {
+ // was there any extra data, then put into inCrypt and clean up
+ if (saveCryptData != null) {
+ inCrypt.clear();
+ inCrypt.put(saveCryptData);
+ inCrypt.flip();
+ saveCryptData = null;
+ }
+ }
}
\ No newline at end of file
diff --git a/src/main/java/org/java_websocket/SocketChannelIOHelper.java b/src/main/java/org/java_websocket/SocketChannelIOHelper.java
index 7e33b3f8e..9a6cfbc51 100644
--- a/src/main/java/org/java_websocket/SocketChannelIOHelper.java
+++ b/src/main/java/org/java_websocket/SocketChannelIOHelper.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -28,77 +28,89 @@
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ByteChannel;
-
-import org.java_websocket.WebSocket.Role;
+import org.java_websocket.enums.Role;
public class SocketChannelIOHelper {
- public static boolean read( final ByteBuffer buf, WebSocketImpl ws, ByteChannel channel ) throws IOException {
- buf.clear();
- int read = channel.read( buf );
- buf.flip();
+ private SocketChannelIOHelper() {
+ throw new IllegalStateException("Utility class");
+ }
+
+ public static boolean read(final ByteBuffer buf, WebSocketImpl ws, ByteChannel channel)
+ throws IOException {
+ buf.clear();
+ int read = channel.read(buf);
+ buf.flip();
- if( read == -1 ) {
- ws.eot();
- return false;
- }
- return read != 0;
- }
+ if (read == -1) {
+ ws.eot();
+ return false;
+ }
+ return read != 0;
+ }
- /**
- * @see WrappedByteChannel#readMore(ByteBuffer)
- * @param buf The ByteBuffer to read from
- * @param ws The WebSocketImpl associated with the channels
- * @param channel The channel to read from
- * @return returns Whether there is more data left which can be obtained via {@link WrappedByteChannel#readMore(ByteBuffer)}
- * @throws IOException May be thrown by {@link WrappedByteChannel#readMore(ByteBuffer)}#
- **/
- public static boolean readMore( final ByteBuffer buf, WebSocketImpl ws, WrappedByteChannel channel ) throws IOException {
- buf.clear();
- int read = channel.readMore( buf );
- buf.flip();
+ /**
+ * @param buf The ByteBuffer to read from
+ * @param ws The WebSocketImpl associated with the channels
+ * @param channel The channel to read from
+ * @return returns Whether there is more data left which can be obtained via {@link
+ * WrappedByteChannel#readMore(ByteBuffer)}
+ * @throws IOException May be thrown by {@link WrappedByteChannel#readMore(ByteBuffer)}#
+ * @see WrappedByteChannel#readMore(ByteBuffer)
+ **/
+ public static boolean readMore(final ByteBuffer buf, WebSocketImpl ws, WrappedByteChannel channel)
+ throws IOException {
+ buf.clear();
+ int read = channel.readMore(buf);
+ buf.flip();
- if( read == -1 ) {
- ws.eot();
- return false;
- }
- return channel.isNeedRead();
- }
+ if (read == -1) {
+ ws.eot();
+ return false;
+ }
+ return channel.isNeedRead();
+ }
- /** Returns whether the whole outQueue has been flushed
- * @param ws The WebSocketImpl associated with the channels
- * @param sockchannel The channel to write to
- * @throws IOException May be thrown by {@link WrappedByteChannel#writeMore()}
- * @return returns Whether there is more data to write
- */
- public static boolean batch( WebSocketImpl ws, ByteChannel sockchannel ) throws IOException {
- ByteBuffer buffer = ws.outQueue.peek();
- WrappedByteChannel c = null;
+ /**
+ * Returns whether the whole outQueue has been flushed
+ *
+ * @param ws The WebSocketImpl associated with the channels
+ * @param sockchannel The channel to write to
+ * @return returns Whether there is more data to write
+ * @throws IOException May be thrown by {@link WrappedByteChannel#writeMore()}
+ */
+ public static boolean batch(WebSocketImpl ws, ByteChannel sockchannel) throws IOException {
+ if (ws == null) {
+ return false;
+ }
+ ByteBuffer buffer = ws.outQueue.peek();
+ WrappedByteChannel c = null;
- if( buffer == null ) {
- if( sockchannel instanceof WrappedByteChannel ) {
- c = (WrappedByteChannel) sockchannel;
- if( c.isNeedWrite() ) {
- c.writeMore();
- }
- }
- } else {
- do {// FIXME writing as much as possible is unfair!!
- /*int written = */sockchannel.write( buffer );
- if( buffer.remaining() > 0 ) {
- return false;
- } else {
- ws.outQueue.poll(); // Buffer finished. Remove it.
- buffer = ws.outQueue.peek();
- }
- } while ( buffer != null );
- }
+ if (buffer == null) {
+ if (sockchannel instanceof WrappedByteChannel) {
+ c = (WrappedByteChannel) sockchannel;
+ if (c.isNeedWrite()) {
+ c.writeMore();
+ }
+ }
+ } else {
+ do {
+ // FIXME writing as much as possible is unfair!!
+ /*int written = */
+ sockchannel.write(buffer);
+ if (buffer.remaining() > 0) {
+ return false;
+ } else {
+ ws.outQueue.poll(); // Buffer finished. Remove it.
+ buffer = ws.outQueue.peek();
+ }
+ } while (buffer != null);
+ }
- if( ws != null && ws.outQueue.isEmpty() && ws.isFlushAndClose() && ws.getDraft() != null && ws.getDraft().getRole() != null && ws.getDraft().getRole() == Role.SERVER ) {//
- synchronized ( ws ) {
- ws.closeConnection();
- }
- }
- return c == null || !((WrappedByteChannel) sockchannel).isNeedWrite();
- }
+ if (ws.outQueue.isEmpty() && ws.isFlushAndClose() && ws.getDraft() != null
+ && ws.getDraft().getRole() != null && ws.getDraft().getRole() == Role.SERVER) {
+ ws.closeConnection();
+ }
+ return c == null || !((WrappedByteChannel) sockchannel).isNeedWrite();
+ }
}
diff --git a/src/main/java/org/java_websocket/WebSocket.java b/src/main/java/org/java_websocket/WebSocket.java
index a0bc3c575..d515f631b 100644
--- a/src/main/java/org/java_websocket/WebSocket.java
+++ b/src/main/java/org/java_websocket/WebSocket.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -27,219 +27,225 @@
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
-import java.nio.channels.NotYetConnectedException;
import java.util.Collection;
-
+import javax.net.ssl.SSLSession;
import org.java_websocket.drafts.Draft;
+import org.java_websocket.enums.Opcode;
+import org.java_websocket.enums.ReadyState;
+import org.java_websocket.exceptions.WebsocketNotConnectedException;
import org.java_websocket.framing.Framedata;
-import org.java_websocket.framing.Framedata.Opcode;
+import org.java_websocket.protocols.IProtocol;
public interface WebSocket {
- /**
- * Enum which represents the states a websocket may be in
- */
- enum Role {
- CLIENT, SERVER
- }
-
- /**
- * Enum which represents the state a websocket may be in
- */
- enum READYSTATE {
- NOT_YET_CONNECTED,
- @Deprecated
- CONNECTING, OPEN, CLOSING, CLOSED
- }
-
- /**
- * The default port of WebSockets, as defined in the spec. If the nullary
- * constructor is used, DEFAULT_PORT will be the port the WebSocketServer
- * is binded to. Note that ports under 1024 usually require root permissions.
- */
- int DEFAULT_PORT = 80;
-
- /**
- * The default wss port of WebSockets, as defined in the spec. If the nullary
- * constructor is used, DEFAULT_WSS_PORT will be the port the WebSocketServer
- * is binded to. Note that ports under 1024 usually require root permissions.
- */
- int DEFAULT_WSS_PORT = 443;
-
- /**
- * sends the closing handshake.
- * may be send in response to an other handshake.
- * @param code the closing code
- * @param message the closing message
- */
- void close( int code, String message );
-
- /**
- * sends the closing handshake.
- * may be send in response to an other handshake.
- * @param code the closing code
- */
- void close( int code );
-
- /** Convenience function which behaves like close(CloseFrame.NORMAL) */
- void close();
-
- /**
- * This will close the connection immediately without a proper close handshake.
- * The code and the message therefore won't be transfered over the wire also they will be forwarded to onClose/onWebsocketClose.
- * @param code the closing code
- * @param message the closing message
- **/
- void closeConnection( int code, String message );
-
- /**
- * Send Text data to the other end.
- *
- * @param text the text data to send
- * @throws NotYetConnectedException websocket is not yet connected
- */
- void send( String text ) throws NotYetConnectedException;
-
- /**
- * Send Binary data (plain bytes) to the other end.
- *
- * @param bytes the binary data to send
- * @throws IllegalArgumentException the data is null
- * @throws NotYetConnectedException websocket is not yet connected
- */
- void send( ByteBuffer bytes ) throws IllegalArgumentException , NotYetConnectedException;
-
- /**
- * Send Binary data (plain bytes) to the other end.
- *
- * @param bytes the byte array to send
- * @throws IllegalArgumentException the data is null
- * @throws NotYetConnectedException websocket is not yet connected
- */
- void send( byte[] bytes ) throws IllegalArgumentException , NotYetConnectedException;
-
- /**
- * Send a frame to the other end
- * @param framedata the frame to send to the other end
- */
- void sendFrame( Framedata framedata );
-
- /**
- * Send a collection of frames to the other end
- * @param frames the frames to send to the other end
- */
- void sendFrame( Collection frames );
-
- /**
- * Send a ping to the other end
- * @throws NotYetConnectedException websocket is not yet connected
- */
- void sendPing() throws NotYetConnectedException;
-
- /**
- * Allows to send continuous/fragmented frames conveniently.
- * For more into on this frame type see http://tools.ietf.org/html/rfc6455#section-5.4
- *
- * If the first frame you send is also the last then it is not a fragmented frame and will received via onMessage instead of onFragmented even though it was send by this method.
- *
- * @param op
- * This is only important for the first frame in the sequence. Opcode.TEXT, Opcode.BINARY are allowed.
- * @param buffer
- * The buffer which contains the payload. It may have no bytes remaining.
- * @param fin
- * true means the current frame is the last in the sequence.
- **/
- void sendFragmentedFrame( Opcode op, ByteBuffer buffer, boolean fin );
-
- /**
- * Checks if the websocket has buffered data
- * @return has the websocket buffered data
- */
- boolean hasBufferedData();
-
- /**
- * Returns the address of the endpoint this socket is connected to, or{@code null} if it is unconnected.
- *
- * @return never returns null
- */
- InetSocketAddress getRemoteSocketAddress();
-
- /**
- * Returns the address of the endpoint this socket is bound to.
- *
- * @return never returns null
- */
- InetSocketAddress getLocalSocketAddress();
-
- /**
- * Is the websocket in the state CONNECTING
- * @return state equals READYSTATE.CONNECTING
- */
- @Deprecated
- boolean isConnecting();
-
- /**
- * Is the websocket in the state OPEN
- * @return state equals READYSTATE.OPEN
- */
- boolean isOpen();
-
- /**
- * Is the websocket in the state CLOSING
- * @return state equals READYSTATE.CLOSING
- */
- boolean isClosing();
-
- /**
- * Returns true when no further frames may be submitted
- * This happens before the socket connection is closed.
- * @return true when no further frames may be submitted
- */
- boolean isFlushAndClose();
-
- /**
- * Is the websocket in the state CLOSED
- * @return state equals READYSTATE.CLOSED
- */
- boolean isClosed();
-
- /**
- * Getter for the draft
- * @return the used draft
- */
- Draft getDraft();
-
- /**
- * Retrieve the WebSocket 'readyState'.
- * This represents the state of the connection.
- * It returns a numerical value, as per W3C WebSockets specs.
- *
- * @return Returns '0 = CONNECTING', '1 = OPEN', '2 = CLOSING' or '3 = CLOSED'
- */
- READYSTATE getReadyState();
-
- /**
- * Returns the HTTP Request-URI as defined by http://tools.ietf.org/html/rfc2616#section-5.1.2
- * If the opening handshake has not yet happened it will return null.
- * @return Returns the decoded path component of this URI.
- **/
- String getResourceDescriptor();
-
- /**
- * Setter for an attachment on the socket connection.
- * The attachment may be of any type.
- *
- * @param attachment The object to be attached to the user
- * @param The type of the attachment
- * @since 1.3.7
- **/
- void setAttachment(T attachment);
-
- /**
- * Getter for the connection attachment.
- *
- * @param The type of the attachment
- * @return Returns the user attachment
- * @since 1.3.7
- **/
- T getAttachment();
+
+ /**
+ * sends the closing handshake. may be send in response to an other handshake.
+ *
+ * @param code the closing code
+ * @param message the closing message
+ */
+ void close(int code, String message);
+
+ /**
+ * sends the closing handshake. may be send in response to an other handshake.
+ *
+ * @param code the closing code
+ */
+ void close(int code);
+
+ /**
+ * Convenience function which behaves like close(CloseFrame.NORMAL)
+ */
+ void close();
+
+ /**
+ * This will close the connection immediately without a proper close handshake. The code and the
+ * message therefore won't be transferred over the wire also they will be forwarded to
+ * onClose/onWebsocketClose.
+ *
+ * @param code the closing code
+ * @param message the closing message
+ **/
+ void closeConnection(int code, String message);
+
+ /**
+ * Send Text data to the other end.
+ *
+ * @param text the text data to send
+ * @throws WebsocketNotConnectedException websocket is not yet connected
+ */
+ void send(String text);
+
+ /**
+ * Send Binary data (plain bytes) to the other end.
+ *
+ * @param bytes the binary data to send
+ * @throws IllegalArgumentException the data is null
+ * @throws WebsocketNotConnectedException websocket is not yet connected
+ */
+ void send(ByteBuffer bytes);
+
+ /**
+ * Send Binary data (plain bytes) to the other end.
+ *
+ * @param bytes the byte array to send
+ * @throws IllegalArgumentException the data is null
+ * @throws WebsocketNotConnectedException websocket is not yet connected
+ */
+ void send(byte[] bytes);
+
+ /**
+ * Send a frame to the other end
+ *
+ * @param framedata the frame to send to the other end
+ */
+ void sendFrame(Framedata framedata);
+
+ /**
+ * Send a collection of frames to the other end
+ *
+ * @param frames the frames to send to the other end
+ */
+ void sendFrame(Collection frames);
+
+ /**
+ * Send a ping to the other end
+ *
+ * @throws WebsocketNotConnectedException websocket is not yet connected
+ */
+ void sendPing();
+
+ /**
+ * Allows to send continuous/fragmented frames conveniently.
For more into on this frame type
+ * see http://tools.ietf.org/html/rfc6455#section-5.4
+ *
+ * If the first frame you send is also the last then it is not a fragmented frame and will
+ * received via onMessage instead of onFragmented even though it was send by this method.
+ *
+ * @param op This is only important for the first frame in the sequence. Opcode.TEXT,
+ * Opcode.BINARY are allowed.
+ * @param buffer The buffer which contains the payload. It may have no bytes remaining.
+ * @param fin true means the current frame is the last in the sequence.
+ **/
+ void sendFragmentedFrame(Opcode op, ByteBuffer buffer, boolean fin);
+
+ /**
+ * Checks if the websocket has buffered data
+ *
+ * @return has the websocket buffered data
+ */
+ boolean hasBufferedData();
+
+ /**
+ * Returns the address of the endpoint this socket is connected to, or {@code null} if it is
+ * unconnected.
+ *
+ * @return the remote socket address or null, if this socket is unconnected
+ */
+ InetSocketAddress getRemoteSocketAddress();
+
+ /**
+ * Returns the address of the endpoint this socket is bound to, or {@code null} if it is not
+ * bound.
+ *
+ * @return the local socket address or null, if this socket is not bound
+ */
+ InetSocketAddress getLocalSocketAddress();
+
+ /**
+ * Is the websocket in the state OPEN
+ *
+ * @return state equals ReadyState.OPEN
+ */
+ boolean isOpen();
+
+ /**
+ * Is the websocket in the state CLOSING
+ *
+ * @return state equals ReadyState.CLOSING
+ */
+ boolean isClosing();
+
+ /**
+ * Returns true when no further frames may be submitted
This happens before the socket
+ * connection is closed.
+ *
+ * @return true when no further frames may be submitted
+ */
+ boolean isFlushAndClose();
+
+ /**
+ * Is the websocket in the state CLOSED
+ *
+ * @return state equals ReadyState.CLOSED
+ */
+ boolean isClosed();
+
+ /**
+ * Getter for the draft
+ *
+ * @return the used draft
+ */
+ Draft getDraft();
+
+ /**
+ * Retrieve the WebSocket 'ReadyState'. This represents the state of the connection. It returns a
+ * numerical value, as per W3C WebSockets specs.
+ *
+ * @return Returns '0 = CONNECTING', '1 = OPEN', '2 = CLOSING' or '3 = CLOSED'
+ */
+ ReadyState getReadyState();
+
+ /**
+ * Returns the HTTP Request-URI as defined by http://tools.ietf.org/html/rfc2616#section-5.1.2
+ * If the opening handshake has not yet happened it will return null.
+ *
+ * @return Returns the decoded path component of this URI.
+ **/
+ String getResourceDescriptor();
+
+ /**
+ * Setter for an attachment on the socket connection. The attachment may be of any type.
+ *
+ * @param attachment The object to be attached to the user
+ * @param The type of the attachment
+ * @since 1.3.7
+ **/
+ void setAttachment(T attachment);
+
+ /**
+ * Getter for the connection attachment.
+ *
+ * @param The type of the attachment
+ * @return Returns the user attachment
+ * @since 1.3.7
+ **/
+ T getAttachment();
+
+ /**
+ * Does this websocket use an encrypted (wss/ssl) or unencrypted (ws) connection
+ *
+ * @return true, if the websocket does use wss and therefore has a SSLSession
+ * @since 1.4.1
+ */
+ boolean hasSSLSupport();
+
+ /**
+ * Returns the ssl session of websocket, if ssl/wss is used for this instance.
+ *
+ * @return the ssl session of this websocket instance
+ * @throws IllegalArgumentException the underlying channel does not use ssl (use hasSSLSupport()
+ * to check)
+ * @since 1.4.1
+ */
+ SSLSession getSSLSession() throws IllegalArgumentException;
+
+ /**
+ * Returns the used Sec-WebSocket-Protocol for this websocket connection
+ *
+ * @return the Sec-WebSocket-Protocol or null, if no draft available
+ * @throws IllegalArgumentException the underlying draft does not support a Sec-WebSocket-Protocol
+ * @since 1.5.2
+ */
+ IProtocol getProtocol();
}
\ No newline at end of file
diff --git a/src/main/java/org/java_websocket/WebSocketAdapter.java b/src/main/java/org/java_websocket/WebSocketAdapter.java
index 9d5435399..d06ca6f91 100644
--- a/src/main/java/org/java_websocket/WebSocketAdapter.java
+++ b/src/main/java/org/java_websocket/WebSocketAdapter.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -36,64 +36,78 @@
import org.java_websocket.handshake.ServerHandshakeBuilder;
/**
- * This class default implements all methods of the WebSocketListener that can be overridden optionally when advances functionalities is needed.
+ * This class default implements all methods of the WebSocketListener that can be overridden
+ * optionally when advances functionalities is needed.
**/
public abstract class WebSocketAdapter implements WebSocketListener {
- /**
- * This default implementation does not do anything. Go ahead and overwrite it.
- *
- * @see org.java_websocket.WebSocketListener#onWebsocketHandshakeReceivedAsServer(WebSocket, Draft, ClientHandshake)
- */
- @Override
- public ServerHandshakeBuilder onWebsocketHandshakeReceivedAsServer( WebSocket conn, Draft draft, ClientHandshake request ) throws InvalidDataException {
- return new HandshakeImpl1Server();
- }
+ private PingFrame pingFrame;
- @Override
- public void onWebsocketHandshakeReceivedAsClient( WebSocket conn, ClientHandshake request, ServerHandshake response ) throws InvalidDataException {
- //To overwrite
- }
+ /**
+ * This default implementation does not do anything. Go ahead and overwrite it.
+ *
+ * @see org.java_websocket.WebSocketListener#onWebsocketHandshakeReceivedAsServer(WebSocket,
+ * Draft, ClientHandshake)
+ */
+ @Override
+ public ServerHandshakeBuilder onWebsocketHandshakeReceivedAsServer(WebSocket conn, Draft draft,
+ ClientHandshake request) throws InvalidDataException {
+ return new HandshakeImpl1Server();
+ }
- /**
- * This default implementation does not do anything which will cause the connections to always progress.
- *
- * @see org.java_websocket.WebSocketListener#onWebsocketHandshakeSentAsClient(WebSocket, ClientHandshake)
- */
- @Override
- public void onWebsocketHandshakeSentAsClient( WebSocket conn, ClientHandshake request ) throws InvalidDataException {
- //To overwrite
- }
+ @Override
+ public void onWebsocketHandshakeReceivedAsClient(WebSocket conn, ClientHandshake request,
+ ServerHandshake response) throws InvalidDataException {
+ //To overwrite
+ }
- /**
- * This default implementation does not do anything. Go ahead and overwrite it
- *
- * @see org.java_websocket.WebSocketListener#onWebsocketMessageFragment(WebSocket, Framedata)
- */
- @Override
- @Deprecated
- public void onWebsocketMessageFragment( WebSocket conn, Framedata frame ) {
- //To overwrite
- }
+ /**
+ * This default implementation does not do anything which will cause the connections to always
+ * progress.
+ *
+ * @see org.java_websocket.WebSocketListener#onWebsocketHandshakeSentAsClient(WebSocket,
+ * ClientHandshake)
+ */
+ @Override
+ public void onWebsocketHandshakeSentAsClient(WebSocket conn, ClientHandshake request)
+ throws InvalidDataException {
+ //To overwrite
+ }
- /**
- * This default implementation will send a pong in response to the received ping.
- * The pong frame will have the same payload as the ping frame.
- *
- * @see org.java_websocket.WebSocketListener#onWebsocketPing(WebSocket, Framedata)
- */
- @Override
- public void onWebsocketPing( WebSocket conn, Framedata f ) {
- conn.sendFrame( new PongFrame( (PingFrame)f ) );
- }
+ /**
+ * This default implementation will send a pong in response to the received ping. The pong frame
+ * will have the same payload as the ping frame.
+ *
+ * @see org.java_websocket.WebSocketListener#onWebsocketPing(WebSocket, Framedata)
+ */
+ @Override
+ public void onWebsocketPing(WebSocket conn, Framedata f) {
+ conn.sendFrame(new PongFrame((PingFrame) f));
+ }
- /**
- * This default implementation does not do anything. Go ahead and overwrite it.
- *
- * @see org.java_websocket.WebSocketListener#onWebsocketPong(WebSocket, Framedata)
- */
- @Override
- public void onWebsocketPong( WebSocket conn, Framedata f ) {
- //To overwrite
- }
+ /**
+ * This default implementation does not do anything. Go ahead and overwrite it.
+ *
+ * @see org.java_websocket.WebSocketListener#onWebsocketPong(WebSocket, Framedata)
+ */
+ @Override
+ public void onWebsocketPong(WebSocket conn, Framedata f) {
+ //To overwrite
+ }
+
+ /**
+ * Default implementation for onPreparePing, returns a (cached) PingFrame that has no application
+ * data.
+ *
+ * @param conn The WebSocket connection from which the ping frame will be sent.
+ * @return PingFrame to be sent.
+ * @see org.java_websocket.WebSocketListener#onPreparePing(WebSocket)
+ */
+ @Override
+ public PingFrame onPreparePing(WebSocket conn) {
+ if (pingFrame == null) {
+ pingFrame = new PingFrame();
+ }
+ return pingFrame;
+ }
}
diff --git a/src/main/java/org/java_websocket/WebSocketFactory.java b/src/main/java/org/java_websocket/WebSocketFactory.java
index fe0f5eb0f..eed6e5ad3 100644
--- a/src/main/java/org/java_websocket/WebSocketFactory.java
+++ b/src/main/java/org/java_websocket/WebSocketFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -26,24 +26,26 @@
package org.java_websocket;
import java.util.List;
-
import org.java_websocket.drafts.Draft;
public interface WebSocketFactory {
- /**
- * Create a new Websocket with the provided listener, drafts and socket
- * @param a The Listener for the WebsocketImpl
- * @param d The draft which should be used
- * @return A WebsocketImpl
- */
- WebSocket createWebSocket( WebSocketAdapter a, Draft d);
- /**
- * Create a new Websocket with the provided listener, drafts and socket
- * @param a The Listener for the WebsocketImpl
- * @param drafts The drafts which should be used
- * @return A WebsocketImpl
- */
- WebSocket createWebSocket( WebSocketAdapter a, List drafts);
+ /**
+ * Create a new Websocket with the provided listener, drafts and socket
+ *
+ * @param a The Listener for the WebsocketImpl
+ * @param d The draft which should be used
+ * @return A WebsocketImpl
+ */
+ WebSocket createWebSocket(WebSocketAdapter a, Draft d);
+
+ /**
+ * Create a new Websocket with the provided listener, drafts and socket
+ *
+ * @param a The Listener for the WebsocketImpl
+ * @param drafts The drafts which should be used
+ * @return A WebsocketImpl
+ */
+ WebSocket createWebSocket(WebSocketAdapter a, List drafts);
}
diff --git a/src/main/java/org/java_websocket/WebSocketImpl.java b/src/main/java/org/java_websocket/WebSocketImpl.java
index 32104afd1..3289aefcf 100644
--- a/src/main/java/org/java_websocket/WebSocketImpl.java
+++ b/src/main/java/org/java_websocket/WebSocketImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -25,28 +25,10 @@
package org.java_websocket;
-import org.java_websocket.drafts.Draft;
-import org.java_websocket.drafts.Draft.CloseHandshakeType;
-import org.java_websocket.drafts.Draft.HandshakeState;
-import org.java_websocket.drafts.Draft_6455;
-import org.java_websocket.exceptions.IncompleteHandshakeException;
-import org.java_websocket.exceptions.InvalidDataException;
-import org.java_websocket.exceptions.InvalidHandshakeException;
-import org.java_websocket.exceptions.WebsocketNotConnectedException;
-import org.java_websocket.framing.CloseFrame;
-import org.java_websocket.framing.Framedata;
-import org.java_websocket.framing.Framedata.Opcode;
-import org.java_websocket.framing.PingFrame;
-import org.java_websocket.handshake.*;
-import org.java_websocket.server.WebSocketServer.WebSocketWorker;
-import org.java_websocket.util.Charsetfunctions;
-
import java.io.IOException;
import java.net.InetSocketAddress;
-import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.ByteChannel;
-import java.nio.channels.NotYetConnectedException;
import java.nio.channels.SelectionKey;
import java.util.ArrayList;
import java.util.Collection;
@@ -54,764 +36,880 @@
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
+import javax.net.ssl.SSLSession;
+import org.java_websocket.drafts.Draft;
+import org.java_websocket.drafts.Draft_6455;
+import org.java_websocket.enums.CloseHandshakeType;
+import org.java_websocket.enums.HandshakeState;
+import org.java_websocket.enums.Opcode;
+import org.java_websocket.enums.ReadyState;
+import org.java_websocket.enums.Role;
+import org.java_websocket.exceptions.IncompleteHandshakeException;
+import org.java_websocket.exceptions.InvalidDataException;
+import org.java_websocket.exceptions.InvalidHandshakeException;
+import org.java_websocket.exceptions.LimitExceededException;
+import org.java_websocket.exceptions.WebsocketNotConnectedException;
+import org.java_websocket.framing.CloseFrame;
+import org.java_websocket.framing.Framedata;
+import org.java_websocket.framing.PingFrame;
+import org.java_websocket.handshake.ClientHandshake;
+import org.java_websocket.handshake.ClientHandshakeBuilder;
+import org.java_websocket.handshake.Handshakedata;
+import org.java_websocket.handshake.ServerHandshake;
+import org.java_websocket.handshake.ServerHandshakeBuilder;
+import org.java_websocket.interfaces.ISSLChannel;
+import org.java_websocket.protocols.IProtocol;
+import org.java_websocket.server.WebSocketServer.WebSocketWorker;
+import org.java_websocket.util.Charsetfunctions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
- * Represents one end (client or server) of a single WebSocketImpl connection.
- * Takes care of the "handshake" phase, then allows for easy sending of
- * text frames, and receiving frames through an event-based model.
+ * Represents one end (client or server) of a single WebSocketImpl connection. Takes care of the
+ * "handshake" phase, then allows for easy sending of text frames, and receiving frames through an
+ * event-based model.
*/
public class WebSocketImpl implements WebSocket {
- public static int RCVBUF = 16384;
-
- /**
- * Activate debug mode for additional infos
- */
- public static boolean DEBUG = false; // must be final in the future in order to take advantage of VM optimization
-
- /**
- * Queue of buffers that need to be sent to the client.
- */
- public final BlockingQueue outQueue;
- /**
- * Queue of buffers that need to be processed
- */
- public final BlockingQueue inQueue;
- /**
- * The listener to notify of WebSocket events.
- */
- private final WebSocketListener wsl;
- public SelectionKey key;
- /**
- * the possibly wrapped channel object whose selection is controlled by {@link #key}
- */
- public ByteChannel channel;
- /**
- * Helper variable meant to store the thread which ( exclusively ) triggers this objects decode method.
- **/
- public volatile WebSocketWorker workerThread; // TODO reset worker?
- /**
- * When true no further frames may be submitted to be sent
- */
- private volatile boolean flushandclosestate = false;
-
- /**
- * The current state of the connection
- */
- private READYSTATE readystate = READYSTATE.NOT_YET_CONNECTED;
-
- /**
- * A list of drafts available for this websocket
- */
- private List knownDrafts;
-
- /**
- * The draft which is used by this websocket
- */
- private Draft draft = null;
-
- /**
- * The role which this websocket takes in the connection
- */
- private Role role;
-
- /**
- * the bytes of an incomplete received handshake
- */
- private ByteBuffer tmpHandshakeBytes = ByteBuffer.allocate( 0 );
-
- /**
- * stores the handshake sent by this websocket ( Role.CLIENT only )
- */
- private ClientHandshake handshakerequest = null;
-
- private String closemessage = null;
- private Integer closecode = null;
- private Boolean closedremotely = null;
-
- private String resourceDescriptor = null;
-
- /**
- * Attribute, when the last pong was recieved
- */
- private long lastPong = System.currentTimeMillis();
-
- /**
- * Attribut to synchronize the write
- */
- private static final Object synchronizeWriteObject = new Object();
-
- /**
- * Attribute to cache a ping frame
- */
- private PingFrame pingFrame;
-
- /**
- * Attribute to store connection attachment
- * @since 1.3.7
- */
- private Object attachment;
-
- /**
- * Creates a websocket with server role
- *
- * @param listener The listener for this instance
- * @param drafts The drafts which should be used
- */
- public WebSocketImpl( WebSocketListener listener, List drafts ) {
- this( listener, ( Draft ) null );
- this.role = Role.SERVER;
- // draft.copyInstance will be called when the draft is first needed
- if( drafts == null || drafts.isEmpty() ) {
- knownDrafts = new ArrayList();
- knownDrafts.add( new Draft_6455() );
- } else {
- knownDrafts = drafts;
- }
- }
-
- /**
- * creates a websocket with client role
- *
- * @param listener The listener for this instance
- * @param draft The draft which should be used
- */
- public WebSocketImpl( WebSocketListener listener, Draft draft ) {
- if( listener == null || ( draft == null && role == Role.SERVER ) )// socket can be null because we want do be able to create the object without already having a bound channel
- throw new IllegalArgumentException( "parameters must not be null" );
- this.outQueue = new LinkedBlockingQueue();
- inQueue = new LinkedBlockingQueue();
- this.wsl = listener;
- this.role = Role.CLIENT;
- if( draft != null )
- this.draft = draft.copyInstance();
- }
-
- @Deprecated
- public WebSocketImpl( WebSocketListener listener, Draft draft, Socket socket ) {
- this( listener, draft );
- }
-
- @Deprecated
- public WebSocketImpl( WebSocketListener listener, List drafts, Socket socket ) {
- this( listener, drafts );
- }
-
- /**
- * Method to decode the provided ByteBuffer
- *
- * @param socketBuffer the ByteBuffer to decode
- */
- public void decode( ByteBuffer socketBuffer ) {
- assert ( socketBuffer.hasRemaining() );
-
- if( DEBUG )
- System.out.println( "process(" + socketBuffer.remaining() + "): {" + ( socketBuffer.remaining() > 1000 ? "too big to display" : new String( socketBuffer.array(), socketBuffer.position(), socketBuffer.remaining() ) ) + '}' );
-
- if( getReadyState() != READYSTATE.NOT_YET_CONNECTED ) {
- if( getReadyState() == READYSTATE.OPEN ) {
- decodeFrames( socketBuffer );
- }
- } else {
- if( decodeHandshake( socketBuffer ) && (!isClosing() && !isClosed())) {
- assert ( tmpHandshakeBytes.hasRemaining() != socketBuffer.hasRemaining() || !socketBuffer.hasRemaining() ); // the buffers will never have remaining bytes at the same time
-
- if( socketBuffer.hasRemaining() ) {
- decodeFrames( socketBuffer );
- } else if( tmpHandshakeBytes.hasRemaining() ) {
- decodeFrames( tmpHandshakeBytes );
- }
- }
- }
- assert ( isClosing() || isFlushAndClose() || !socketBuffer.hasRemaining() );
- }
-
- /**
- * Returns whether the handshake phase has is completed.
- * In case of a broken handshake this will be never the case.
- **/
- private boolean decodeHandshake( ByteBuffer socketBufferNew ) {
- ByteBuffer socketBuffer;
- if( tmpHandshakeBytes.capacity() == 0 ) {
- socketBuffer = socketBufferNew;
- } else {
- if( tmpHandshakeBytes.remaining() < socketBufferNew.remaining() ) {
- ByteBuffer buf = ByteBuffer.allocate( tmpHandshakeBytes.capacity() + socketBufferNew.remaining() );
- tmpHandshakeBytes.flip();
- buf.put( tmpHandshakeBytes );
- tmpHandshakeBytes = buf;
- }
-
- tmpHandshakeBytes.put( socketBufferNew );
- tmpHandshakeBytes.flip();
- socketBuffer = tmpHandshakeBytes;
- }
- socketBuffer.mark();
- try {
- HandshakeState handshakestate;
- try {
- if( role == Role.SERVER ) {
- if( draft == null ) {
- for( Draft d : knownDrafts ) {
- d = d.copyInstance();
- try {
- d.setParseMode( role );
- socketBuffer.reset();
- Handshakedata tmphandshake = d.translateHandshake( socketBuffer );
- if( !( tmphandshake instanceof ClientHandshake ) ) {
- closeConnectionDueToWrongHandshake( new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "wrong http function" ) );
- return false;
- }
- ClientHandshake handshake = ( ClientHandshake ) tmphandshake;
- handshakestate = d.acceptHandshakeAsServer( handshake );
- if( handshakestate == HandshakeState.MATCHED ) {
- resourceDescriptor = handshake.getResourceDescriptor();
- ServerHandshakeBuilder response;
- try {
- response = wsl.onWebsocketHandshakeReceivedAsServer( this, d, handshake );
- } catch ( InvalidDataException e ) {
- closeConnectionDueToWrongHandshake( e );
- return false;
- } catch ( RuntimeException e ) {
- wsl.onWebsocketError( this, e );
- closeConnectionDueToInternalServerError( e );
- return false;
- }
- write( d.createHandshake( d.postProcessHandshakeResponseAsServer( handshake, response ), role ) );
- draft = d;
- open( handshake );
- return true;
- }
- } catch ( InvalidHandshakeException e ) {
- // go on with an other draft
- }
- }
- if( draft == null ) {
- closeConnectionDueToWrongHandshake( new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "no draft matches" ) );
- }
- return false;
- } else {
- // special case for multiple step handshakes
- Handshakedata tmphandshake = draft.translateHandshake( socketBuffer );
- if( !( tmphandshake instanceof ClientHandshake ) ) {
- flushAndClose( CloseFrame.PROTOCOL_ERROR, "wrong http function", false );
- return false;
- }
- ClientHandshake handshake = ( ClientHandshake ) tmphandshake;
- handshakestate = draft.acceptHandshakeAsServer( handshake );
-
- if( handshakestate == HandshakeState.MATCHED ) {
- open( handshake );
- return true;
- } else {
- close( CloseFrame.PROTOCOL_ERROR, "the handshake did finaly not match" );
- }
- return false;
- }
- } else if( role == Role.CLIENT ) {
- draft.setParseMode( role );
- Handshakedata tmphandshake = draft.translateHandshake( socketBuffer );
- if( !( tmphandshake instanceof ServerHandshake ) ) {
- flushAndClose( CloseFrame.PROTOCOL_ERROR, "wrong http function", false );
- return false;
- }
- ServerHandshake handshake = ( ServerHandshake ) tmphandshake;
- handshakestate = draft.acceptHandshakeAsClient( handshakerequest, handshake );
- if( handshakestate == HandshakeState.MATCHED ) {
- try {
- wsl.onWebsocketHandshakeReceivedAsClient( this, handshakerequest, handshake );
- } catch ( InvalidDataException e ) {
- flushAndClose( e.getCloseCode(), e.getMessage(), false );
- return false;
- } catch ( RuntimeException e ) {
- wsl.onWebsocketError( this, e );
- flushAndClose( CloseFrame.NEVER_CONNECTED, e.getMessage(), false );
- return false;
- }
- open( handshake );
- return true;
- } else {
- close( CloseFrame.PROTOCOL_ERROR, "draft " + draft + " refuses handshake" );
- }
- }
- } catch ( InvalidHandshakeException e ) {
- close( e );
- }
- } catch ( IncompleteHandshakeException e ) {
- if( tmpHandshakeBytes.capacity() == 0 ) {
- socketBuffer.reset();
- int newsize = e.getPreferedSize();
- if( newsize == 0 ) {
- newsize = socketBuffer.capacity() + 16;
- } else {
- assert ( e.getPreferedSize() >= socketBuffer.remaining() );
- }
- tmpHandshakeBytes = ByteBuffer.allocate( newsize );
-
- tmpHandshakeBytes.put( socketBufferNew );
- // tmpHandshakeBytes.flip();
- } else {
- tmpHandshakeBytes.position( tmpHandshakeBytes.limit() );
- tmpHandshakeBytes.limit( tmpHandshakeBytes.capacity() );
- }
- }
- return false;
- }
-
- private void decodeFrames( ByteBuffer socketBuffer ) {
- List frames;
- try {
- frames = draft.translateFrame( socketBuffer );
- for( Framedata f : frames ) {
- if( DEBUG )
- System.out.println( "matched frame: " + f );
- draft.processFrame( this, f );
- }
- } catch ( InvalidDataException e1 ) {
- wsl.onWebsocketError( this, e1 );
- close( e1 );
- return;
- }
- }
-
- /**
- * Close the connection if the received handshake was not correct
- *
- * @param exception the InvalidDataException causing this problem
- */
- private void closeConnectionDueToWrongHandshake( InvalidDataException exception ) {
- write( generateHttpResponseDueToError( 404 ) );
- flushAndClose( exception.getCloseCode(), exception.getMessage(), false );
- }
-
- /**
- * Close the connection if there was a server error by a RuntimeException
- *
- * @param exception the RuntimeException causing this problem
- */
- private void closeConnectionDueToInternalServerError( RuntimeException exception ) {
- write( generateHttpResponseDueToError( 500 ) );
- flushAndClose( CloseFrame.NEVER_CONNECTED, exception.getMessage(), false );
- }
-
- /**
- * Generate a simple response for the corresponding endpoint to indicate some error
- *
- * @param errorCode the http error code
- * @return the complete response as ByteBuffer
- */
- private ByteBuffer generateHttpResponseDueToError( int errorCode ) {
- String errorCodeDescription;
- switch(errorCode) {
- case 404:
- errorCodeDescription = "404 WebSocket Upgrade Failure";
- break;
- case 500:
- default:
- errorCodeDescription = "500 Internal Server Error";
- }
- return ByteBuffer.wrap( Charsetfunctions.asciiBytes( "HTTP/1.1 " + errorCodeDescription + "\r\nContent-Type: text/html\nServer: TooTallNate Java-WebSocket\r\nContent-Length: " + ( 48 + errorCodeDescription.length() ) + "\r\n\r\n" + errorCodeDescription + "
" ) );
- }
-
- public synchronized void close( int code, String message, boolean remote ) {
- if( getReadyState() != READYSTATE.CLOSING && readystate != READYSTATE.CLOSED ) {
- if( getReadyState() == READYSTATE.OPEN ) {
- if( code == CloseFrame.ABNORMAL_CLOSE ) {
- assert ( !remote );
- setReadyState( READYSTATE.CLOSING );
- flushAndClose( code, message, false );
- return;
- }
- if( draft.getCloseHandshakeType() != CloseHandshakeType.NONE ) {
- try {
- if( !remote ) {
- try {
- wsl.onWebsocketCloseInitiated( this, code, message );
- } catch ( RuntimeException e ) {
- wsl.onWebsocketError( this, e );
- }
- }
- if( isOpen() ) {
- CloseFrame closeFrame = new CloseFrame();
- closeFrame.setReason( message );
- closeFrame.setCode( code );
- closeFrame.isValid();
- sendFrame( closeFrame );
- }
- } catch ( InvalidDataException e ) {
- wsl.onWebsocketError( this, e );
- flushAndClose( CloseFrame.ABNORMAL_CLOSE, "generated frame is invalid", false );
- }
- }
- flushAndClose( code, message, remote );
- } else if( code == CloseFrame.FLASHPOLICY ) {
- assert ( remote );
- flushAndClose( CloseFrame.FLASHPOLICY, message, true );
- } else if( code == CloseFrame.PROTOCOL_ERROR ) { // this endpoint found a PROTOCOL_ERROR
- flushAndClose( code, message, remote );
- } else {
- flushAndClose( CloseFrame.NEVER_CONNECTED, message, false );
- }
- setReadyState( READYSTATE.CLOSING );
- tmpHandshakeBytes = null;
- return;
- }
- }
-
- @Override
- public void close( int code, String message ) {
- close( code, message, false );
- }
-
- /**
- * This will close the connection immediately without a proper close handshake.
- * The code and the message therefore won't be transfered over the wire also they will be forwarded to onClose/onWebsocketClose.
- *
- * @param code the closing code
- * @param message the closing message
- * @param remote Indicates who "generated" code.
- * true means that this endpoint received the code from the other endpoint.
- * false means this endpoint decided to send the given code,
- * remote may also be true if this endpoint started the closing handshake since the other endpoint may not simply echo the code but close the connection the same time this endpoint does do but with an other code.
- **/
- public synchronized void closeConnection( int code, String message, boolean remote ) {
- if( getReadyState() == READYSTATE.CLOSED ) {
- return;
- }
- //Methods like eot() call this method without calling onClose(). Due to that reason we have to adjust the readystate manually
- if( getReadyState() == READYSTATE.OPEN ) {
- if( code == CloseFrame.ABNORMAL_CLOSE ) {
- setReadyState( READYSTATE.CLOSING );
- }
- }
- if( key != null ) {
- // key.attach( null ); //see issue #114
- key.cancel();
- }
- if( channel != null ) {
- try {
- channel.close();
- } catch ( IOException e ) {
- if( e.getMessage().equals( "Broken pipe" ) ) {
- if( WebSocketImpl.DEBUG ) {
- System.out.println( "Caught IOException: Broken pipe during closeConnection()" );
- }
- } else {
- wsl.onWebsocketError( this, e );
- }
- }
- }
- try {
- this.wsl.onWebsocketClose( this, code, message, remote );
- } catch ( RuntimeException e ) {
- wsl.onWebsocketError( this, e );
- }
- if( draft != null )
- draft.reset();
- handshakerequest = null;
- setReadyState( READYSTATE.CLOSED );
- }
-
- protected void closeConnection( int code, boolean remote ) {
- closeConnection( code, "", remote );
- }
-
- public void closeConnection() {
- if( closedremotely == null ) {
- throw new IllegalStateException( "this method must be used in conjuction with flushAndClose" );
- }
- closeConnection( closecode, closemessage, closedremotely );
- }
-
- public void closeConnection( int code, String message ) {
- closeConnection( code, message, false );
- }
-
- public synchronized void flushAndClose( int code, String message, boolean remote ) {
- if( flushandclosestate ) {
- return;
- }
- closecode = code;
- closemessage = message;
- closedremotely = remote;
-
- flushandclosestate = true;
-
- wsl.onWriteDemand( this ); // ensures that all outgoing frames are flushed before closing the connection
- try {
- wsl.onWebsocketClosing( this, code, message, remote );
- } catch ( RuntimeException e ) {
- wsl.onWebsocketError( this, e );
- }
- if( draft != null )
- draft.reset();
- handshakerequest = null;
- }
-
- public void eot() {
- if( getReadyState() == READYSTATE.NOT_YET_CONNECTED ) {
- closeConnection( CloseFrame.NEVER_CONNECTED, true );
- } else if( flushandclosestate ) {
- closeConnection( closecode, closemessage, closedremotely );
- } else if( draft.getCloseHandshakeType() == CloseHandshakeType.NONE ) {
- closeConnection( CloseFrame.NORMAL, true );
- } else if( draft.getCloseHandshakeType() == CloseHandshakeType.ONEWAY ) {
- if( role == Role.SERVER )
- closeConnection( CloseFrame.ABNORMAL_CLOSE, true );
- else
- closeConnection( CloseFrame.NORMAL, true );
- } else {
- closeConnection( CloseFrame.ABNORMAL_CLOSE, true );
- }
- }
-
- @Override
- public void close( int code ) {
- close( code, "", false );
- }
-
- public void close( InvalidDataException e ) {
- close( e.getCloseCode(), e.getMessage(), false );
- }
-
- /**
- * Send Text data to the other end.
- *
- * @throws NotYetConnectedException websocket is not yet connected
- */
- @Override
- public void send( String text ) throws WebsocketNotConnectedException {
- if( text == null )
- throw new IllegalArgumentException( "Cannot send 'null' data to a WebSocketImpl." );
- send( draft.createFrames( text, role == Role.CLIENT ) );
- }
-
- /**
- * Send Binary data (plain bytes) to the other end.
- *
- * @throws IllegalArgumentException the data is null
- * @throws NotYetConnectedException websocket is not yet connected
- */
- @Override
- public void send( ByteBuffer bytes ) throws IllegalArgumentException, WebsocketNotConnectedException {
- if( bytes == null )
- throw new IllegalArgumentException( "Cannot send 'null' data to a WebSocketImpl." );
- send( draft.createFrames( bytes, role == Role.CLIENT ) );
- }
-
- @Override
- public void send( byte[] bytes ) throws IllegalArgumentException, WebsocketNotConnectedException {
- send( ByteBuffer.wrap( bytes ) );
- }
-
- private void send( Collection frames ) {
- if( !isOpen() ) {
- throw new WebsocketNotConnectedException();
- }
- if( frames == null ) {
- throw new IllegalArgumentException();
- }
- ArrayList outgoingFrames = new ArrayList();
- for( Framedata f : frames ) {
- if( DEBUG )
- System.out.println( "send frame: " + f );
- outgoingFrames.add( draft.createBinaryFrame( f ) );
- }
- write( outgoingFrames );
- }
-
- @Override
- public void sendFragmentedFrame( Opcode op, ByteBuffer buffer, boolean fin ) {
- send( draft.continuousFrame( op, buffer, fin ) );
- }
-
- @Override
- public void sendFrame( Collection frames ) {
- send( frames );
- }
-
- @Override
- public void sendFrame( Framedata framedata ) {
- send( Collections.singletonList( framedata ) );
- }
-
- public void sendPing() throws NotYetConnectedException {
- if( pingFrame == null ) {
- pingFrame = new PingFrame();
- }
- sendFrame( pingFrame );
- }
-
- @Override
- public boolean hasBufferedData() {
- return !this.outQueue.isEmpty();
- }
-
- public void startHandshake( ClientHandshakeBuilder handshakedata ) throws InvalidHandshakeException {
- // Store the Handshake Request we are about to send
- this.handshakerequest = draft.postProcessHandshakeRequestAsClient( handshakedata );
-
- resourceDescriptor = handshakedata.getResourceDescriptor();
- assert ( resourceDescriptor != null );
-
- // Notify Listener
- try {
- wsl.onWebsocketHandshakeSentAsClient( this, this.handshakerequest );
- } catch ( InvalidDataException e ) {
- // Stop if the client code throws an exception
- throw new InvalidHandshakeException( "Handshake data rejected by client." );
- } catch ( RuntimeException e ) {
- wsl.onWebsocketError( this, e );
- throw new InvalidHandshakeException( "rejected because of" + e );
- }
-
- // Send
- write( draft.createHandshake( this.handshakerequest, role ) );
- }
-
- private void write( ByteBuffer buf ) {
- if( DEBUG )
- System.out.println( "write(" + buf.remaining() + "): {" + ( buf.remaining() > 1000 ? "too big to display" : new String( buf.array() ) ) + '}' );
-
- outQueue.add( buf );
- /*try {
- outQueue.put( buf );
- } catch ( InterruptedException e ) {
- write( buf );
- Thread.currentThread().interrupt(); // keep the interrupted status
- e.printStackTrace();
- }*/
- wsl.onWriteDemand( this );
- }
-
- /**
- * Write a list of bytebuffer (frames in binary form) into the outgoing queue
- *
- * @param bufs the list of bytebuffer
- */
- private void write( List bufs ) {
- synchronized(synchronizeWriteObject) {
- for( ByteBuffer b : bufs ) {
- write( b );
- }
- }
- }
-
- private void open( Handshakedata d ) {
- if( DEBUG )
- System.out.println( "open using draft: " + draft );
- setReadyState( READYSTATE.OPEN );
- try {
- wsl.onWebsocketOpen( this, d );
- } catch ( RuntimeException e ) {
- wsl.onWebsocketError( this, e );
- }
- }
-
- @Override
- @Deprecated
- public boolean isConnecting() {
- assert ( !flushandclosestate || getReadyState() == READYSTATE.CONNECTING );
- return getReadyState() == READYSTATE.CONNECTING; // ifflushandclosestate
- }
-
- @Override
- public boolean isOpen() {
- return getReadyState() == READYSTATE.OPEN;
- }
-
- @Override
- public boolean isClosing() {
- return getReadyState() == READYSTATE.CLOSING;
- }
-
- @Override
- public boolean isFlushAndClose() {
- return flushandclosestate;
- }
-
- @Override
- public boolean isClosed() {
- return getReadyState() == READYSTATE.CLOSED;
- }
-
- @Override
- public READYSTATE getReadyState() {
- return readystate;
- }
-
- private void setReadyState( READYSTATE readystate ) {
- this.readystate = readystate;
- }
-
- @Override
- public int hashCode() {
- return super.hashCode();
- }
-
- @Override
- public String toString() {
- return super.toString(); // its nice to be able to set breakpoints here
- }
-
- @Override
- public InetSocketAddress getRemoteSocketAddress() {
- return wsl.getRemoteSocketAddress( this );
- }
-
- @Override
- public InetSocketAddress getLocalSocketAddress() {
- return wsl.getLocalSocketAddress( this );
- }
-
- @Override
- public Draft getDraft() {
- return draft;
- }
-
- @Override
- public void close() {
- close( CloseFrame.NORMAL );
- }
-
- @Override
- public String getResourceDescriptor() {
- return resourceDescriptor;
- }
-
- /**
- * Getter for the last pong recieved
- *
- * @return the timestamp for the last recieved pong
- */
- long getLastPong() {
- return lastPong;
- }
-
- /**
- * Update the timestamp when the last pong was received
- */
- public void updateLastPong() {
- this.lastPong = System.currentTimeMillis();
- }
-
- /**
- * Getter for the websocket listener
- *
- * @return the websocket listener associated with this instance
- */
- public WebSocketListener getWebSocketListener() {
- return wsl;
- }
-
- @Override
- @SuppressWarnings("unchecked")
- public T getAttachment() {
- return (T) attachment;
- }
-
- @Override
- public void setAttachment(T attachment) {
- this.attachment = attachment;
- }
+
+ /**
+ * The default port of WebSockets, as defined in the spec. If the nullary constructor is used,
+ * DEFAULT_PORT will be the port the WebSocketServer is binded to. Note that ports under 1024
+ * usually require root permissions.
+ */
+ public static final int DEFAULT_PORT = 80;
+
+ /**
+ * The default wss port of WebSockets, as defined in the spec. If the nullary constructor is used,
+ * DEFAULT_WSS_PORT will be the port the WebSocketServer is binded to. Note that ports under 1024
+ * usually require root permissions.
+ */
+ public static final int DEFAULT_WSS_PORT = 443;
+
+ /**
+ * Logger instance
+ *
+ * @since 1.4.0
+ */
+ private final Logger log = LoggerFactory.getLogger(WebSocketImpl.class);
+
+ /**
+ * Queue of buffers that need to be sent to the client.
+ */
+ public final BlockingQueue outQueue;
+ /**
+ * Queue of buffers that need to be processed
+ */
+ public final BlockingQueue inQueue;
+ /**
+ * The listener to notify of WebSocket events.
+ */
+ private final WebSocketListener wsl;
+
+ private SelectionKey key;
+
+ /**
+ * the possibly wrapped channel object whose selection is controlled by {@link #key}
+ */
+ private ByteChannel channel;
+ /**
+ * Helper variable meant to store the thread which ( exclusively ) triggers this objects decode
+ * method.
+ **/
+ private WebSocketWorker workerThread;
+ /**
+ * When true no further frames may be submitted to be sent
+ */
+ private boolean flushandclosestate = false;
+
+ /**
+ * The current state of the connection
+ */
+ private volatile ReadyState readyState = ReadyState.NOT_YET_CONNECTED;
+
+ /**
+ * A list of drafts available for this websocket
+ */
+ private List knownDrafts;
+
+ /**
+ * The draft which is used by this websocket
+ */
+ private Draft draft = null;
+
+ /**
+ * The role which this websocket takes in the connection
+ */
+ private Role role;
+
+ /**
+ * the bytes of an incomplete received handshake
+ */
+ private ByteBuffer tmpHandshakeBytes = ByteBuffer.allocate(0);
+
+ /**
+ * stores the handshake sent by this websocket ( Role.CLIENT only )
+ */
+ private ClientHandshake handshakerequest = null;
+
+ private String closemessage = null;
+ private Integer closecode = null;
+ private Boolean closedremotely = null;
+
+ private String resourceDescriptor = null;
+
+ /**
+ * Attribute, when the last pong was received
+ */
+ private long lastPong = System.nanoTime();
+
+ /**
+ * Attribut to synchronize the write
+ */
+ private final Object synchronizeWriteObject = new Object();
+
+ /**
+ * Attribute to store connection attachment
+ *
+ * @since 1.3.7
+ */
+ private Object attachment;
+
+ /**
+ * Creates a websocket with server role
+ *
+ * @param listener The listener for this instance
+ * @param drafts The drafts which should be used
+ */
+ public WebSocketImpl(WebSocketListener listener, List drafts) {
+ this(listener, (Draft) null);
+ this.role = Role.SERVER;
+ // draft.copyInstance will be called when the draft is first needed
+ if (drafts == null || drafts.isEmpty()) {
+ knownDrafts = new ArrayList<>();
+ knownDrafts.add(new Draft_6455());
+ } else {
+ knownDrafts = drafts;
+ }
+ }
+
+ /**
+ * creates a websocket with client role
+ *
+ * @param listener The listener for this instance
+ * @param draft The draft which should be used
+ */
+ public WebSocketImpl(WebSocketListener listener, Draft draft) {
+ // socket can be null because we want do be able to create the object without already having a bound channel
+ if (listener == null || (draft == null && role == Role.SERVER)) {
+ throw new IllegalArgumentException("parameters must not be null");
+ }
+ this.outQueue = new LinkedBlockingQueue<>();
+ inQueue = new LinkedBlockingQueue<>();
+ this.wsl = listener;
+ this.role = Role.CLIENT;
+ if (draft != null) {
+ this.draft = draft.copyInstance();
+ }
+ }
+
+ /**
+ * Method to decode the provided ByteBuffer
+ *
+ * @param socketBuffer the ByteBuffer to decode
+ */
+ public void decode(ByteBuffer socketBuffer) {
+ assert (socketBuffer.hasRemaining());
+ if (log.isTraceEnabled()) {
+ log.trace("process({}): ({})", socketBuffer.remaining(),
+ (socketBuffer.remaining() > 1000 ? "too big to display"
+ : new String(socketBuffer.array(), socketBuffer.position(), socketBuffer.remaining())));
+ }
+ if (readyState != ReadyState.NOT_YET_CONNECTED) {
+ if (readyState == ReadyState.OPEN) {
+ decodeFrames(socketBuffer);
+ }
+ } else {
+ if (decodeHandshake(socketBuffer) && (!isClosing() && !isClosed())) {
+ assert (tmpHandshakeBytes.hasRemaining() != socketBuffer.hasRemaining() || !socketBuffer
+ .hasRemaining()); // the buffers will never have remaining bytes at the same time
+ if (socketBuffer.hasRemaining()) {
+ decodeFrames(socketBuffer);
+ } else if (tmpHandshakeBytes.hasRemaining()) {
+ decodeFrames(tmpHandshakeBytes);
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns whether the handshake phase has is completed. In case of a broken handshake this will
+ * be never the case.
+ **/
+ private boolean decodeHandshake(ByteBuffer socketBufferNew) {
+ ByteBuffer socketBuffer;
+ if (tmpHandshakeBytes.capacity() == 0) {
+ socketBuffer = socketBufferNew;
+ } else {
+ if (tmpHandshakeBytes.remaining() < socketBufferNew.remaining()) {
+ ByteBuffer buf = ByteBuffer
+ .allocate(tmpHandshakeBytes.capacity() + socketBufferNew.remaining());
+ tmpHandshakeBytes.flip();
+ buf.put(tmpHandshakeBytes);
+ tmpHandshakeBytes = buf;
+ }
+
+ tmpHandshakeBytes.put(socketBufferNew);
+ tmpHandshakeBytes.flip();
+ socketBuffer = tmpHandshakeBytes;
+ }
+ socketBuffer.mark();
+ try {
+ HandshakeState handshakestate;
+ try {
+ if (role == Role.SERVER) {
+ if (draft == null) {
+ for (Draft d : knownDrafts) {
+ d = d.copyInstance();
+ try {
+ d.setParseMode(role);
+ socketBuffer.reset();
+ Handshakedata tmphandshake = d.translateHandshake(socketBuffer);
+ if (!(tmphandshake instanceof ClientHandshake)) {
+ log.trace("Closing due to wrong handshake");
+ closeConnectionDueToWrongHandshake(
+ new InvalidDataException(CloseFrame.PROTOCOL_ERROR, "wrong http function"));
+ return false;
+ }
+ ClientHandshake handshake = (ClientHandshake) tmphandshake;
+ handshakestate = d.acceptHandshakeAsServer(handshake);
+ if (handshakestate == HandshakeState.MATCHED) {
+ resourceDescriptor = handshake.getResourceDescriptor();
+ ServerHandshakeBuilder response;
+ try {
+ response = wsl.onWebsocketHandshakeReceivedAsServer(this, d, handshake);
+ } catch (InvalidDataException e) {
+ log.trace("Closing due to wrong handshake. Possible handshake rejection", e);
+ closeConnectionDueToWrongHandshake(e);
+ return false;
+ } catch (RuntimeException e) {
+ log.error("Closing due to internal server error", e);
+ wsl.onWebsocketError(this, e);
+ closeConnectionDueToInternalServerError(e);
+ return false;
+ }
+ write(d.createHandshake(
+ d.postProcessHandshakeResponseAsServer(handshake, response)));
+ draft = d;
+ open(handshake);
+ return true;
+ }
+ } catch (InvalidHandshakeException e) {
+ // go on with an other draft
+ }
+ }
+ if (draft == null) {
+ log.trace("Closing due to protocol error: no draft matches");
+ closeConnectionDueToWrongHandshake(
+ new InvalidDataException(CloseFrame.PROTOCOL_ERROR, "no draft matches"));
+ }
+ return false;
+ } else {
+ // special case for multiple step handshakes
+ Handshakedata tmphandshake = draft.translateHandshake(socketBuffer);
+ if (!(tmphandshake instanceof ClientHandshake)) {
+ log.trace("Closing due to protocol error: wrong http function");
+ flushAndClose(CloseFrame.PROTOCOL_ERROR, "wrong http function", false);
+ return false;
+ }
+ ClientHandshake handshake = (ClientHandshake) tmphandshake;
+ handshakestate = draft.acceptHandshakeAsServer(handshake);
+
+ if (handshakestate == HandshakeState.MATCHED) {
+ open(handshake);
+ return true;
+ } else {
+ log.trace("Closing due to protocol error: the handshake did finally not match");
+ close(CloseFrame.PROTOCOL_ERROR, "the handshake did finally not match");
+ }
+ return false;
+ }
+ } else if (role == Role.CLIENT) {
+ draft.setParseMode(role);
+ Handshakedata tmphandshake = draft.translateHandshake(socketBuffer);
+ if (!(tmphandshake instanceof ServerHandshake)) {
+ log.trace("Closing due to protocol error: wrong http function");
+ flushAndClose(CloseFrame.PROTOCOL_ERROR, "wrong http function", false);
+ return false;
+ }
+ ServerHandshake handshake = (ServerHandshake) tmphandshake;
+ handshakestate = draft.acceptHandshakeAsClient(handshakerequest, handshake);
+ if (handshakestate == HandshakeState.MATCHED) {
+ try {
+ wsl.onWebsocketHandshakeReceivedAsClient(this, handshakerequest, handshake);
+ } catch (InvalidDataException e) {
+ log.trace("Closing due to invalid data exception. Possible handshake rejection", e);
+ flushAndClose(e.getCloseCode(), e.getMessage(), false);
+ return false;
+ } catch (RuntimeException e) {
+ log.error("Closing since client was never connected", e);
+ wsl.onWebsocketError(this, e);
+ flushAndClose(CloseFrame.NEVER_CONNECTED, e.getMessage(), false);
+ return false;
+ }
+ open(handshake);
+ return true;
+ } else {
+ log.trace("Closing due to protocol error: draft {} refuses handshake", draft);
+ close(CloseFrame.PROTOCOL_ERROR, "draft " + draft + " refuses handshake");
+ }
+ }
+ } catch (InvalidHandshakeException e) {
+ log.trace("Closing due to invalid handshake", e);
+ close(e);
+ }
+ } catch (IncompleteHandshakeException e) {
+ if (tmpHandshakeBytes.capacity() == 0) {
+ socketBuffer.reset();
+ int newsize = e.getPreferredSize();
+ if (newsize == 0) {
+ newsize = socketBuffer.capacity() + 16;
+ } else {
+ assert (e.getPreferredSize() >= socketBuffer.remaining());
+ }
+ tmpHandshakeBytes = ByteBuffer.allocate(newsize);
+
+ tmpHandshakeBytes.put(socketBufferNew);
+ // tmpHandshakeBytes.flip();
+ } else {
+ tmpHandshakeBytes.position(tmpHandshakeBytes.limit());
+ tmpHandshakeBytes.limit(tmpHandshakeBytes.capacity());
+ }
+ }
+ return false;
+ }
+
+ private void decodeFrames(ByteBuffer socketBuffer) {
+ List frames;
+ try {
+ frames = draft.translateFrame(socketBuffer);
+ for (Framedata f : frames) {
+ log.trace("matched frame: {}", f);
+ draft.processFrame(this, f);
+ }
+ } catch (LimitExceededException e) {
+ if (e.getLimit() == Integer.MAX_VALUE) {
+ log.error("Closing due to invalid size of frame", e);
+ wsl.onWebsocketError(this, e);
+ }
+ close(e);
+ } catch (InvalidDataException e) {
+ log.error("Closing due to invalid data in frame", e);
+ wsl.onWebsocketError(this, e);
+ close(e);
+ } catch (VirtualMachineError | ThreadDeath | LinkageError e) {
+ log.error("Got fatal error during frame processing");
+ throw e;
+ } catch (Error e) {
+ log.error("Closing web socket due to an error during frame processing");
+ Exception exception = new Exception(e);
+ wsl.onWebsocketError(this, exception);
+ String errorMessage = "Got error " + e.getClass().getName();
+ close(CloseFrame.UNEXPECTED_CONDITION, errorMessage);
+ }
+ }
+
+ /**
+ * Close the connection if the received handshake was not correct
+ *
+ * @param exception the InvalidDataException causing this problem
+ */
+ private void closeConnectionDueToWrongHandshake(InvalidDataException exception) {
+ write(generateHttpResponseDueToError(404));
+ flushAndClose(exception.getCloseCode(), exception.getMessage(), false);
+ }
+
+ /**
+ * Close the connection if there was a server error by a RuntimeException
+ *
+ * @param exception the RuntimeException causing this problem
+ */
+ private void closeConnectionDueToInternalServerError(RuntimeException exception) {
+ write(generateHttpResponseDueToError(500));
+ flushAndClose(CloseFrame.NEVER_CONNECTED, exception.getMessage(), false);
+ }
+
+ /**
+ * Generate a simple response for the corresponding endpoint to indicate some error
+ *
+ * @param errorCode the http error code
+ * @return the complete response as ByteBuffer
+ */
+ private ByteBuffer generateHttpResponseDueToError(int errorCode) {
+ String errorCodeDescription;
+ switch (errorCode) {
+ case 404:
+ errorCodeDescription = "404 WebSocket Upgrade Failure";
+ break;
+ case 500:
+ default:
+ errorCodeDescription = "500 Internal Server Error";
+ }
+ return ByteBuffer.wrap(Charsetfunctions.asciiBytes("HTTP/1.1 " + errorCodeDescription
+ + "\r\nContent-Type: text/html\r\nServer: TooTallNate Java-WebSocket\r\nContent-Length: "
+ + (48 + errorCodeDescription.length()) + "\r\n\r\n"
+ + errorCodeDescription + "
"));
+ }
+
+ public synchronized void close(int code, String message, boolean remote) {
+ if (readyState != ReadyState.CLOSING && readyState != ReadyState.CLOSED) {
+ if (readyState == ReadyState.OPEN) {
+ if (code == CloseFrame.ABNORMAL_CLOSE) {
+ assert (!remote);
+ readyState = ReadyState.CLOSING;
+ flushAndClose(code, message, false);
+ return;
+ }
+ if (draft.getCloseHandshakeType() != CloseHandshakeType.NONE) {
+ try {
+ if (!remote) {
+ try {
+ wsl.onWebsocketCloseInitiated(this, code, message);
+ } catch (RuntimeException e) {
+ wsl.onWebsocketError(this, e);
+ }
+ }
+ if (isOpen()) {
+ CloseFrame closeFrame = new CloseFrame();
+ closeFrame.setReason(message);
+ closeFrame.setCode(code);
+ closeFrame.isValid();
+ sendFrame(closeFrame);
+ }
+ } catch (InvalidDataException e) {
+ log.error("generated frame is invalid", e);
+ wsl.onWebsocketError(this, e);
+ flushAndClose(CloseFrame.ABNORMAL_CLOSE, "generated frame is invalid", false);
+ }
+ }
+ flushAndClose(code, message, remote);
+ } else if (code == CloseFrame.FLASHPOLICY) {
+ assert (remote);
+ flushAndClose(CloseFrame.FLASHPOLICY, message, true);
+ } else if (code == CloseFrame.PROTOCOL_ERROR) { // this endpoint found a PROTOCOL_ERROR
+ flushAndClose(code, message, remote);
+ } else {
+ flushAndClose(CloseFrame.NEVER_CONNECTED, message, false);
+ }
+ readyState = ReadyState.CLOSING;
+ tmpHandshakeBytes = null;
+ return;
+ }
+ }
+
+ @Override
+ public void close(int code, String message) {
+ close(code, message, false);
+ }
+
+ /**
+ * This will close the connection immediately without a proper close handshake. The code and the
+ * message therefore won't be transferred over the wire also they will be forwarded to
+ * onClose/onWebsocketClose.
+ *
+ * @param code the closing code
+ * @param message the closing message
+ * @param remote Indicates who "generated" code.
+ * true means that this endpoint received the code from
+ * the other endpoint.
false means this endpoint decided to send the given
+ * code,
+ * remote may also be true if this endpoint started the closing
+ * handshake since the other endpoint may not simply echo the code but
+ * close the connection the same time this endpoint does do but with an other
+ * code.
+ **/
+ public synchronized void closeConnection(int code, String message, boolean remote) {
+ if (readyState == ReadyState.CLOSED) {
+ return;
+ }
+ //Methods like eot() call this method without calling onClose(). Due to that reason we have to adjust the ReadyState manually
+ if (readyState == ReadyState.OPEN) {
+ if (code == CloseFrame.ABNORMAL_CLOSE) {
+ readyState = ReadyState.CLOSING;
+ }
+ }
+ if (key != null) {
+ // key.attach( null ); //see issue #114
+ key.cancel();
+ }
+ if (channel != null) {
+ try {
+ channel.close();
+ } catch (IOException e) {
+ if (e.getMessage() != null && e.getMessage().equals("Broken pipe")) {
+ log.trace("Caught IOException: Broken pipe during closeConnection()", e);
+ } else {
+ log.error("Exception during channel.close()", e);
+ wsl.onWebsocketError(this, e);
+ }
+ }
+ }
+ try {
+ this.wsl.onWebsocketClose(this, code, message, remote);
+ } catch (RuntimeException e) {
+
+ wsl.onWebsocketError(this, e);
+ }
+ if (draft != null) {
+ draft.reset();
+ }
+ handshakerequest = null;
+ readyState = ReadyState.CLOSED;
+ }
+
+ protected void closeConnection(int code, boolean remote) {
+ closeConnection(code, "", remote);
+ }
+
+ public void closeConnection() {
+ if (closedremotely == null) {
+ throw new IllegalStateException("this method must be used in conjunction with flushAndClose");
+ }
+ closeConnection(closecode, closemessage, closedremotely);
+ }
+
+ public void closeConnection(int code, String message) {
+ closeConnection(code, message, false);
+ }
+
+ public synchronized void flushAndClose(int code, String message, boolean remote) {
+ if (flushandclosestate) {
+ return;
+ }
+ closecode = code;
+ closemessage = message;
+ closedremotely = remote;
+
+ flushandclosestate = true;
+
+ wsl.onWriteDemand(
+ this); // ensures that all outgoing frames are flushed before closing the connection
+ try {
+ wsl.onWebsocketClosing(this, code, message, remote);
+ } catch (RuntimeException e) {
+ log.error("Exception in onWebsocketClosing", e);
+ wsl.onWebsocketError(this, e);
+ }
+ if (draft != null) {
+ draft.reset();
+ }
+ handshakerequest = null;
+ }
+
+ public void eot() {
+ if (readyState == ReadyState.NOT_YET_CONNECTED) {
+ closeConnection(CloseFrame.NEVER_CONNECTED, true);
+ } else if (flushandclosestate) {
+ closeConnection(closecode, closemessage, closedremotely);
+ } else if (draft.getCloseHandshakeType() == CloseHandshakeType.NONE) {
+ closeConnection(CloseFrame.NORMAL, true);
+ } else if (draft.getCloseHandshakeType() == CloseHandshakeType.ONEWAY) {
+ if (role == Role.SERVER) {
+ closeConnection(CloseFrame.ABNORMAL_CLOSE, true);
+ } else {
+ closeConnection(CloseFrame.NORMAL, true);
+ }
+ } else {
+ closeConnection(CloseFrame.ABNORMAL_CLOSE, true);
+ }
+ }
+
+ @Override
+ public void close(int code) {
+ close(code, "", false);
+ }
+
+ public void close(InvalidDataException e) {
+ close(e.getCloseCode(), e.getMessage(), false);
+ }
+
+ /**
+ * Send Text data to the other end.
+ *
+ * @throws WebsocketNotConnectedException websocket is not yet connected
+ */
+ @Override
+ public void send(String text) {
+ if (text == null) {
+ throw new IllegalArgumentException("Cannot send 'null' data to a WebSocketImpl.");
+ }
+ send(draft.createFrames(text, role == Role.CLIENT));
+ }
+
+ /**
+ * Send Binary data (plain bytes) to the other end.
+ *
+ * @throws IllegalArgumentException the data is null
+ * @throws WebsocketNotConnectedException websocket is not yet connected
+ */
+ @Override
+ public void send(ByteBuffer bytes) {
+ if (bytes == null) {
+ throw new IllegalArgumentException("Cannot send 'null' data to a WebSocketImpl.");
+ }
+ send(draft.createFrames(bytes, role == Role.CLIENT));
+ }
+
+ @Override
+ public void send(byte[] bytes) {
+ send(ByteBuffer.wrap(bytes));
+ }
+
+ private void send(Collection frames) {
+ if (!isOpen()) {
+ throw new WebsocketNotConnectedException();
+ }
+ if (frames == null) {
+ throw new IllegalArgumentException();
+ }
+ ArrayList outgoingFrames = new ArrayList<>();
+ for (Framedata f : frames) {
+ log.trace("send frame: {}", f);
+ outgoingFrames.add(draft.createBinaryFrame(f));
+ }
+ write(outgoingFrames);
+ }
+
+ @Override
+ public void sendFragmentedFrame(Opcode op, ByteBuffer buffer, boolean fin) {
+ send(draft.continuousFrame(op, buffer, fin));
+ }
+
+ @Override
+ public void sendFrame(Collection frames) {
+ send(frames);
+ }
+
+ @Override
+ public void sendFrame(Framedata framedata) {
+ send(Collections.singletonList(framedata));
+ }
+
+ public void sendPing() throws NullPointerException {
+ // Gets a PingFrame from WebSocketListener(wsl) and sends it.
+ PingFrame pingFrame = wsl.onPreparePing(this);
+ if (pingFrame == null) {
+ throw new NullPointerException(
+ "onPreparePing(WebSocket) returned null. PingFrame to sent can't be null.");
+ }
+ sendFrame(pingFrame);
+ }
+
+ @Override
+ public boolean hasBufferedData() {
+ return !this.outQueue.isEmpty();
+ }
+
+ public void startHandshake(ClientHandshakeBuilder handshakedata)
+ throws InvalidHandshakeException {
+ // Store the Handshake Request we are about to send
+ this.handshakerequest = draft.postProcessHandshakeRequestAsClient(handshakedata);
+
+ resourceDescriptor = handshakedata.getResourceDescriptor();
+ assert (resourceDescriptor != null);
+
+ // Notify Listener
+ try {
+ wsl.onWebsocketHandshakeSentAsClient(this, this.handshakerequest);
+ } catch (InvalidDataException e) {
+ // Stop if the client code throws an exception
+ throw new InvalidHandshakeException("Handshake data rejected by client.");
+ } catch (RuntimeException e) {
+ log.error("Exception in startHandshake", e);
+ wsl.onWebsocketError(this, e);
+ throw new InvalidHandshakeException("rejected because of " + e);
+ }
+
+ // Send
+ write(draft.createHandshake(this.handshakerequest));
+ }
+
+ private void write(ByteBuffer buf) {
+ log.trace("write({}): {}", buf.remaining(),
+ buf.remaining() > 1000 ? "too big to display" : new String(buf.array()));
+
+ outQueue.add(buf);
+ wsl.onWriteDemand(this);
+ }
+
+ /**
+ * Write a list of bytebuffer (frames in binary form) into the outgoing queue
+ *
+ * @param bufs the list of bytebuffer
+ */
+ private void write(List bufs) {
+ synchronized (synchronizeWriteObject) {
+ for (ByteBuffer b : bufs) {
+ write(b);
+ }
+ }
+ }
+
+ private void open(Handshakedata d) {
+ log.trace("open using draft: {}", draft);
+ readyState = ReadyState.OPEN;
+ updateLastPong();
+ try {
+ wsl.onWebsocketOpen(this, d);
+ } catch (RuntimeException e) {
+ wsl.onWebsocketError(this, e);
+ }
+ }
+
+ @Override
+ public boolean isOpen() {
+ return readyState == ReadyState.OPEN;
+ }
+
+ @Override
+ public boolean isClosing() {
+ return readyState == ReadyState.CLOSING;
+ }
+
+ @Override
+ public boolean isFlushAndClose() {
+ return flushandclosestate;
+ }
+
+ @Override
+ public boolean isClosed() {
+ return readyState == ReadyState.CLOSED;
+ }
+
+ @Override
+ public ReadyState getReadyState() {
+ return readyState;
+ }
+
+ /**
+ * @param key the selection key of this implementation
+ */
+ public void setSelectionKey(SelectionKey key) {
+ this.key = key;
+ }
+
+ /**
+ * @return the selection key of this implementation
+ */
+ public SelectionKey getSelectionKey() {
+ return key;
+ }
+
+ @Override
+ public String toString() {
+ return super.toString(); // its nice to be able to set breakpoints here
+ }
+
+ @Override
+ public InetSocketAddress getRemoteSocketAddress() {
+ return wsl.getRemoteSocketAddress(this);
+ }
+
+ @Override
+ public InetSocketAddress getLocalSocketAddress() {
+ return wsl.getLocalSocketAddress(this);
+ }
+
+ @Override
+ public Draft getDraft() {
+ return draft;
+ }
+
+ @Override
+ public void close() {
+ close(CloseFrame.NORMAL);
+ }
+
+ @Override
+ public String getResourceDescriptor() {
+ return resourceDescriptor;
+ }
+
+ /**
+ * Getter for the last pong received
+ *
+ * @return the timestamp for the last received pong
+ */
+ long getLastPong() {
+ return lastPong;
+ }
+
+ /**
+ * Update the timestamp when the last pong was received
+ */
+ public void updateLastPong() {
+ this.lastPong = System.nanoTime();
+ }
+
+ /**
+ * Getter for the websocket listener
+ *
+ * @return the websocket listener associated with this instance
+ */
+ public WebSocketListener getWebSocketListener() {
+ return wsl;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public T getAttachment() {
+ return (T) attachment;
+ }
+
+ @Override
+ public boolean hasSSLSupport() {
+ return channel instanceof ISSLChannel;
+ }
+
+ @Override
+ public SSLSession getSSLSession() {
+ if (!hasSSLSupport()) {
+ throw new IllegalArgumentException(
+ "This websocket uses ws instead of wss. No SSLSession available.");
+ }
+ return ((ISSLChannel) channel).getSSLEngine().getSession();
+ }
+
+ @Override
+ public IProtocol getProtocol() {
+ if (draft == null) {
+ return null;
+ }
+ if (!(draft instanceof Draft_6455)) {
+ throw new IllegalArgumentException("This draft does not support Sec-WebSocket-Protocol");
+ }
+ return ((Draft_6455) draft).getProtocol();
+ }
+
+ @Override
+ public void setAttachment(T attachment) {
+ this.attachment = attachment;
+ }
+
+ public ByteChannel getChannel() {
+ return channel;
+ }
+
+ public void setChannel(ByteChannel channel) {
+ this.channel = channel;
+ }
+
+ public WebSocketWorker getWorkerThread() {
+ return workerThread;
+ }
+
+ public void setWorkerThread(WebSocketWorker workerThread) {
+ this.workerThread = workerThread;
+ }
+
}
diff --git a/src/main/java/org/java_websocket/WebSocketListener.java b/src/main/java/org/java_websocket/WebSocketListener.java
index b486ac464..f0b21d526 100644
--- a/src/main/java/org/java_websocket/WebSocketListener.java
+++ b/src/main/java/org/java_websocket/WebSocketListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -27,185 +27,174 @@
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
-
import org.java_websocket.drafts.Draft;
import org.java_websocket.exceptions.InvalidDataException;
import org.java_websocket.framing.CloseFrame;
import org.java_websocket.framing.Framedata;
+import org.java_websocket.framing.PingFrame;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.handshake.Handshakedata;
import org.java_websocket.handshake.ServerHandshake;
import org.java_websocket.handshake.ServerHandshakeBuilder;
/**
- * Implemented by WebSocketClient and WebSocketServer.
- * The methods within are called by WebSocket.
- * Almost every method takes a first parameter conn which represents the source of the respective event.
+ * Implemented by WebSocketClient and WebSocketServer. The methods within are
+ * called by WebSocket. Almost every method takes a first parameter conn which represents
+ * the source of the respective event.
*/
public interface WebSocketListener {
- /**
- * Called on the server side when the socket connection is first established, and the WebSocket
- * handshake has been received. This method allows to deny connections based on the received handshake.
- * By default this method only requires protocol compliance.
- *
- * @param conn
- * The WebSocket related to this event
- * @param draft
- * The protocol draft the client uses to connect
- * @param request
- * The opening http message send by the client. Can be used to access additional fields like cookies.
- * @return Returns an incomplete handshake containing all optional fields
- * @throws InvalidDataException
- * Throwing this exception will cause this handshake to be rejected
- */
- ServerHandshakeBuilder onWebsocketHandshakeReceivedAsServer( WebSocket conn, Draft draft, ClientHandshake request ) throws InvalidDataException;
-
- /**
- * Called on the client side when the socket connection is first established, and the WebSocketImpl
- * handshake response has been received.
- *
- * @param conn
- * The WebSocket related to this event
- * @param request
- * The handshake initially send out to the server by this websocket.
- * @param response
- * The handshake the server sent in response to the request.
- * @throws InvalidDataException
- * Allows the client to reject the connection with the server in respect of its handshake response.
- */
- void onWebsocketHandshakeReceivedAsClient( WebSocket conn, ClientHandshake request, ServerHandshake response ) throws InvalidDataException;
-
- /**
- * Called on the client side when the socket connection is first established, and the WebSocketImpl
- * handshake has just been sent.
- *
- * @param conn
- * The WebSocket related to this event
- * @param request
- * The handshake sent to the server by this websocket
- * @throws InvalidDataException
- * Allows the client to stop the connection from progressing
- */
- void onWebsocketHandshakeSentAsClient( WebSocket conn, ClientHandshake request ) throws InvalidDataException;
-
- /**
- * Called when an entire text frame has been received. Do whatever you want
- * here...
- *
- * @param conn
- * The WebSocket instance this event is occurring on.
- * @param message
- * The UTF-8 decoded message that was received.
- */
- void onWebsocketMessage( WebSocket conn, String message );
-
- /**
- * Called when an entire binary frame has been received. Do whatever you want
- * here...
- *
- * @param conn
- * The WebSocket instance this event is occurring on.
- * @param blob
- * The binary message that was received.
- */
- void onWebsocketMessage( WebSocket conn, ByteBuffer blob );
-
- /**
- * Called when a frame fragment has been recieved
- *
- * This method will be removed in a future version since the lib will also call the respective onWebsocketMessage method
- * @param conn
- * The WebSocket instance this event is occurring on.
- * @param frame The fragmented frame
- */
- @Deprecated
- void onWebsocketMessageFragment( WebSocket conn, Framedata frame );
-
- /**
- * Called after onHandshakeReceived returns true.
- * Indicates that a complete WebSocket connection has been established,
- * and we are ready to send/receive data.
- *
- * @param conn The WebSocket instance this event is occuring on.
- * @param d The handshake of the websocket instance
- */
- void onWebsocketOpen( WebSocket conn, Handshakedata d );
-
- /**
- * Called after WebSocket#close is explicity called, or when the
- * other end of the WebSocket connection is closed.
- *
- * @param ws The WebSocket instance this event is occuring on.
- * @param code The codes can be looked up here: {@link CloseFrame}
- * @param reason Additional information string
- * @param remote Returns whether or not the closing of the connection was initiated by the remote host.
- */
- void onWebsocketClose( WebSocket ws, int code, String reason, boolean remote );
-
- /** Called as soon as no further frames are accepted
- *
- * @param ws The WebSocket instance this event is occuring on.
- * @param code The codes can be looked up here: {@link CloseFrame}
- * @param reason Additional information string
- * @param remote Returns whether or not the closing of the connection was initiated by the remote host.
- */
- void onWebsocketClosing( WebSocket ws, int code, String reason, boolean remote );
-
- /** send when this peer sends a close handshake
- *
- * @param ws The WebSocket instance this event is occuring on.
- * @param code The codes can be looked up here: {@link CloseFrame}
- * @param reason Additional information string
- */
- void onWebsocketCloseInitiated( WebSocket ws, int code, String reason );
-
- /**
- * Called if an exception worth noting occurred.
- * If an error causes the connection to fail onClose will be called additionally afterwards.
- *
- * @param conn The WebSocket instance this event is occuring on.
- * @param ex
- * The exception that occurred.
- * Might be null if the exception is not related to any specific connection. For example if the server port could not be bound.
- */
- void onWebsocketError( WebSocket conn, Exception ex );
-
- /**
- * Called a ping frame has been received.
- * This method must send a corresponding pong by itself.
- *
- * @param conn The WebSocket instance this event is occuring on.
- * @param f The ping frame. Control frames may contain payload.
- */
- void onWebsocketPing( WebSocket conn, Framedata f );
-
- /**
- * Called when a pong frame is received.
- *
- * @param conn The WebSocket instance this event is occuring on.
- * @param f The pong frame. Control frames may contain payload.
- **/
- void onWebsocketPong( WebSocket conn, Framedata f );
-
- /** This method is used to inform the selector thread that there is data queued to be written to the socket.
- * @param conn The WebSocket instance this event is occuring on.
- */
- void onWriteDemand( WebSocket conn );
-
- /**
- * @see WebSocket#getLocalSocketAddress()
- *
- * @param conn The WebSocket instance this event is occuring on.
- * @return Returns the address of the endpoint this socket is bound to.
- */
- InetSocketAddress getLocalSocketAddress( WebSocket conn );
-
- /**
- * @see WebSocket#getRemoteSocketAddress()
- *
- * @param conn The WebSocket instance this event is occuring on.
- * @return Returns the address of the endpoint this socket is connected to, or{@code null} if it is unconnected.
- */
- InetSocketAddress getRemoteSocketAddress( WebSocket conn );
+ /**
+ * Called on the server side when the socket connection is first established, and the WebSocket
+ * handshake has been received. This method allows to deny connections based on the received
+ * handshake.
By default this method only requires protocol compliance.
+ *
+ * @param conn The WebSocket related to this event
+ * @param draft The protocol draft the client uses to connect
+ * @param request The opening http message send by the client. Can be used to access additional
+ * fields like cookies.
+ * @return Returns an incomplete handshake containing all optional fields
+ * @throws InvalidDataException Throwing this exception will cause this handshake to be rejected
+ */
+ ServerHandshakeBuilder onWebsocketHandshakeReceivedAsServer(WebSocket conn, Draft draft,
+ ClientHandshake request) throws InvalidDataException;
+
+ /**
+ * Called on the client side when the socket connection is first established, and the
+ * WebSocketImpl handshake response has been received.
+ *
+ * @param conn The WebSocket related to this event
+ * @param request The handshake initially send out to the server by this websocket.
+ * @param response The handshake the server sent in response to the request.
+ * @throws InvalidDataException Allows the client to reject the connection with the server in
+ * respect of its handshake response.
+ */
+ void onWebsocketHandshakeReceivedAsClient(WebSocket conn, ClientHandshake request,
+ ServerHandshake response) throws InvalidDataException;
+
+ /**
+ * Called on the client side when the socket connection is first established, and the
+ * WebSocketImpl handshake has just been sent.
+ *
+ * @param conn The WebSocket related to this event
+ * @param request The handshake sent to the server by this websocket
+ * @throws InvalidDataException Allows the client to stop the connection from progressing
+ */
+ void onWebsocketHandshakeSentAsClient(WebSocket conn, ClientHandshake request)
+ throws InvalidDataException;
+
+ /**
+ * Called when an entire text frame has been received. Do whatever you want here...
+ *
+ * @param conn The WebSocket instance this event is occurring on.
+ * @param message The UTF-8 decoded message that was received.
+ */
+ void onWebsocketMessage(WebSocket conn, String message);
+
+ /**
+ * Called when an entire binary frame has been received. Do whatever you want here...
+ *
+ * @param conn The WebSocket instance this event is occurring on.
+ * @param blob The binary message that was received.
+ */
+ void onWebsocketMessage(WebSocket conn, ByteBuffer blob);
+
+ /**
+ * Called after onHandshakeReceived returns true. Indicates that a complete
+ * WebSocket connection has been established, and we are ready to send/receive data.
+ *
+ * @param conn The WebSocket instance this event is occurring on.
+ * @param d The handshake of the websocket instance
+ */
+ void onWebsocketOpen(WebSocket conn, Handshakedata d);
+
+ /**
+ * Called after WebSocket#close is explicity called, or when the other end of the
+ * WebSocket connection is closed.
+ *
+ * @param ws The WebSocket instance this event is occurring on.
+ * @param code The codes can be looked up here: {@link CloseFrame}
+ * @param reason Additional information string
+ * @param remote Returns whether or not the closing of the connection was initiated by the remote
+ * host.
+ */
+ void onWebsocketClose(WebSocket ws, int code, String reason, boolean remote);
+
+ /**
+ * Called as soon as no further frames are accepted
+ *
+ * @param ws The WebSocket instance this event is occurring on.
+ * @param code The codes can be looked up here: {@link CloseFrame}
+ * @param reason Additional information string
+ * @param remote Returns whether or not the closing of the connection was initiated by the remote
+ * host.
+ */
+ void onWebsocketClosing(WebSocket ws, int code, String reason, boolean remote);
+
+ /**
+ * send when this peer sends a close handshake
+ *
+ * @param ws The WebSocket instance this event is occurring on.
+ * @param code The codes can be looked up here: {@link CloseFrame}
+ * @param reason Additional information string
+ */
+ void onWebsocketCloseInitiated(WebSocket ws, int code, String reason);
+
+ /**
+ * Called if an exception worth noting occurred. If an error causes the connection to fail onClose
+ * will be called additionally afterwards.
+ *
+ * @param conn The WebSocket instance this event is occurring on.
+ * @param ex The exception that occurred.
Might be null if the exception is not related to
+ * any specific connection. For example if the server port could not be bound.
+ */
+ void onWebsocketError(WebSocket conn, Exception ex);
+
+ /**
+ * Called a ping frame has been received. This method must send a corresponding pong by itself.
+ *
+ * @param conn The WebSocket instance this event is occurring on.
+ * @param f The ping frame. Control frames may contain payload.
+ */
+ void onWebsocketPing(WebSocket conn, Framedata f);
+
+ /**
+ * Called just before a ping frame is sent, in order to allow users to customize their ping frame
+ * data.
+ *
+ * @param conn The WebSocket connection from which the ping frame will be sent.
+ * @return PingFrame to be sent.
+ */
+ PingFrame onPreparePing(WebSocket conn);
+
+ /**
+ * Called when a pong frame is received.
+ *
+ * @param conn The WebSocket instance this event is occurring on.
+ * @param f The pong frame. Control frames may contain payload.
+ **/
+ void onWebsocketPong(WebSocket conn, Framedata f);
+
+ /**
+ * This method is used to inform the selector thread that there is data queued to be written to
+ * the socket.
+ *
+ * @param conn The WebSocket instance this event is occurring on.
+ */
+ void onWriteDemand(WebSocket conn);
+
+ /**
+ * @param conn The WebSocket instance this event is occurring on.
+ * @return Returns the address of the endpoint this socket is bound to.
+ * @see WebSocket#getLocalSocketAddress()
+ */
+ InetSocketAddress getLocalSocketAddress(WebSocket conn);
+
+ /**
+ * @param conn The WebSocket instance this event is occurring on.
+ * @return Returns the address of the endpoint this socket is connected to, or{@code null} if it
+ * is unconnected.
+ * @see WebSocket#getRemoteSocketAddress()
+ */
+ InetSocketAddress getRemoteSocketAddress(WebSocket conn);
}
diff --git a/src/main/java/org/java_websocket/WebSocketServerFactory.java b/src/main/java/org/java_websocket/WebSocketServerFactory.java
index 53ad237ce..825aa2165 100644
--- a/src/main/java/org/java_websocket/WebSocketServerFactory.java
+++ b/src/main/java/org/java_websocket/WebSocketServerFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -25,36 +25,37 @@
package org.java_websocket;
-import org.java_websocket.drafts.Draft;
-
import java.io.IOException;
import java.nio.channels.ByteChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.List;
+import org.java_websocket.drafts.Draft;
/**
* Interface to encapsulate the required methods for a websocket factory
*/
public interface WebSocketServerFactory extends WebSocketFactory {
- @Override
- WebSocketImpl createWebSocket( WebSocketAdapter a, Draft d);
- @Override
- WebSocketImpl createWebSocket( WebSocketAdapter a, List drafts );
+ @Override
+ WebSocketImpl createWebSocket(WebSocketAdapter a, Draft d);
+
+ @Override
+ WebSocketImpl createWebSocket(WebSocketAdapter a, List drafts);
- /**
- * Allows to wrap the Socketchannel( key.channel() ) to insert a protocol layer( like ssl or proxy authentication) beyond the ws layer.
- *
- * @param channel The SocketChannel to wrap
- * @param key a SelectionKey of an open SocketChannel.
- * @return The channel on which the read and write operations will be performed.
- * @throws IOException may be thrown while writing on the channel
- */
- ByteChannel wrapChannel(SocketChannel channel, SelectionKey key ) throws IOException;
+ /**
+ * Allows to wrap the SocketChannel( key.channel() ) to insert a protocol layer( like ssl or proxy
+ * authentication) beyond the ws layer.
+ *
+ * @param channel The SocketChannel to wrap
+ * @param key a SelectionKey of an open SocketChannel.
+ * @return The channel on which the read and write operations will be performed.
+ * @throws IOException may be thrown while writing on the channel
+ */
+ ByteChannel wrapChannel(SocketChannel channel, SelectionKey key) throws IOException;
- /**
- * Allows to shutdown the websocket factory for a clean shutdown
- */
- void close();
+ /**
+ * Allows to shutdown the websocket factory for a clean shutdown
+ */
+ void close();
}
diff --git a/src/main/java/org/java_websocket/WrappedByteChannel.java b/src/main/java/org/java_websocket/WrappedByteChannel.java
index b4e646e4e..8dee57db0 100644
--- a/src/main/java/org/java_websocket/WrappedByteChannel.java
+++ b/src/main/java/org/java_websocket/WrappedByteChannel.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -30,39 +30,47 @@
import java.nio.channels.ByteChannel;
public interface WrappedByteChannel extends ByteChannel {
- /**
- * returns whether writeMore should be called write additional data.
- * @return is a additional write needed
- */
- boolean isNeedWrite();
+ /**
+ * returns whether writeMore should be called write additional data.
+ *
+ * @return is a additional write needed
+ */
+ boolean isNeedWrite();
- /**
- * Gets called when {@link #isNeedWrite()} ()} requires a additional rite
- * @throws IOException may be thrown due to an error while writing
- */
- void writeMore() throws IOException;
+ /**
+ * Gets called when {@link #isNeedWrite()} ()} requires a additional rite
+ *
+ * @throws IOException may be thrown due to an error while writing
+ */
+ void writeMore() throws IOException;
- /**
- * returns whether readMore should be called to fetch data which has been decoded but not yet been returned.
- *
- * @see #read(ByteBuffer)
- * @see #readMore(ByteBuffer)
- * @return is a additional read needed
- **/
- boolean isNeedRead();
- /**
- * This function does not read data from the underlying channel at all. It is just a way to fetch data which has already be received or decoded but was but was not yet returned to the user.
- * This could be the case when the decoded data did not fit into the buffer the user passed to {@link #read(ByteBuffer)}.
- * @param dst the destiny of the read
- * @return the amount of remaining data
- * @throws IOException when a error occurred during unwrapping
- **/
- int readMore( ByteBuffer dst ) throws IOException;
+ /**
+ * returns whether readMore should be called to fetch data which has been decoded but not yet been
+ * returned.
+ *
+ * @return is a additional read needed
+ * @see #read(ByteBuffer)
+ * @see #readMore(ByteBuffer)
+ **/
+ boolean isNeedRead();
- /**
- * This function returns the blocking state of the channel
- * @return is the channel blocking
- */
- boolean isBlocking();
+ /**
+ * This function does not read data from the underlying channel at all. It is just a way to fetch
+ * data which has already be received or decoded but was but was not yet returned to the user.
+ * This could be the case when the decoded data did not fit into the buffer the user passed to
+ * {@link #read(ByteBuffer)}.
+ *
+ * @param dst the destiny of the read
+ * @return the amount of remaining data
+ * @throws IOException when a error occurred during unwrapping
+ **/
+ int readMore(ByteBuffer dst) throws IOException;
+
+ /**
+ * This function returns the blocking state of the channel
+ *
+ * @return is the channel blocking
+ */
+ boolean isBlocking();
}
diff --git a/src/test/java/org/java_websocket/AllTests.java b/src/main/java/org/java_websocket/client/DnsResolver.java
similarity index 60%
rename from src/test/java/org/java_websocket/AllTests.java
rename to src/main/java/org/java_websocket/client/DnsResolver.java
index 07ab5545b..ec9f17f06 100644
--- a/src/test/java/org/java_websocket/AllTests.java
+++ b/src/main/java/org/java_websocket/client/DnsResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -23,24 +23,29 @@
* OTHER DEALINGS IN THE SOFTWARE.
*/
-package org.java_websocket;
+package org.java_websocket.client;
-import org.junit.runner.RunWith;
-import org.junit.runners.Suite;
+import java.net.InetAddress;
+import java.net.URI;
+import java.net.UnknownHostException;
-
-@RunWith(Suite.class)
-@Suite.SuiteClasses({
- org.java_websocket.util.ByteBufferUtilsTest.class,
- org.java_websocket.client.AllClientTests.class,
- org.java_websocket.drafts.AllDraftTests.class,
- org.java_websocket.issues.AllIssueTests.class,
- org.java_websocket.misc.AllMiscTests.class,
- org.java_websocket.protocols.AllProtocolTests.class,
- org.java_websocket.framing.AllFramingTests.class
-})
/**
- * Start all tests
+ * Users may implement this interface to override the default DNS lookup offered by the OS.
+ *
+ * @since 1.4.1
*/
-public class AllTests {
+public interface DnsResolver {
+
+ /**
+ * Resolves the IP address for the given URI.
+ *
+ * This method should never return null. If it's not able to resolve the IP address then it should
+ * throw an UnknownHostException
+ *
+ * @param uri The URI to be resolved
+ * @return The resolved IP address
+ * @throws UnknownHostException if no IP address for the uri could be found.
+ */
+ InetAddress resolve(URI uri) throws UnknownHostException;
+
}
diff --git a/src/main/java/org/java_websocket/client/WebSocketClient.java b/src/main/java/org/java_websocket/client/WebSocketClient.java
index 00e40fda0..0e38326d3 100644
--- a/src/main/java/org/java_websocket/client/WebSocketClient.java
+++ b/src/main/java/org/java_websocket/client/WebSocketClient.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -28,751 +28,998 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.Socket;
import java.net.URI;
+import java.net.UnknownHostException;
import java.nio.ByteBuffer;
-import java.nio.channels.NotYetConnectedException;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
+import java.util.TreeMap;
import java.util.concurrent.CountDownLatch;
-
-import javax.net.ssl.SSLContext;
+import java.util.concurrent.TimeUnit;
+import javax.net.SocketFactory;
import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
-
import org.java_websocket.AbstractWebSocket;
import org.java_websocket.WebSocket;
import org.java_websocket.WebSocketImpl;
import org.java_websocket.drafts.Draft;
import org.java_websocket.drafts.Draft_6455;
+import org.java_websocket.enums.Opcode;
+import org.java_websocket.enums.ReadyState;
import org.java_websocket.exceptions.InvalidHandshakeException;
import org.java_websocket.framing.CloseFrame;
import org.java_websocket.framing.Framedata;
-import org.java_websocket.framing.Framedata.Opcode;
import org.java_websocket.handshake.HandshakeImpl1Client;
import org.java_websocket.handshake.Handshakedata;
import org.java_websocket.handshake.ServerHandshake;
+import org.java_websocket.protocols.IProtocol;
/**
- * A subclass must implement at least onOpen, onClose, and onMessage to be
- * useful. At runtime the user is expected to establish a connection via {@link #connect()}, then receive events like {@link #onMessage(String)} via the overloaded methods and to {@link #send(String)} data to the server.
+ * A subclass must implement at least onOpen, onClose, and
+ * onMessage to be useful. At runtime the user is expected to establish a connection via
+ * {@link #connect()}, then receive events like {@link #onMessage(String)} via the overloaded
+ * methods and to {@link #send(String)} data to the server.
*/
public abstract class WebSocketClient extends AbstractWebSocket implements Runnable, WebSocket {
- /**
- * The URI this channel is supposed to connect to.
- */
- protected URI uri = null;
-
- /**
- * The underlying engine
- */
- private WebSocketImpl engine = null;
-
- /**
- * The socket for this WebSocketClient
- */
- private Socket socket = null;
-
- /**
- * The used OutputStream
- */
- private OutputStream ostream;
-
- /**
- * The used proxy, if any
- */
- private Proxy proxy = Proxy.NO_PROXY;
-
- /**
- * The thread to write outgoing message
- */
- private Thread writeThread;
-
- /**
- * The draft to use
- */
- private Draft draft;
-
- /**
- * The additional headers to use
- */
- private Map headers;
-
- /**
- * The latch for connectBlocking()
- */
- private CountDownLatch connectLatch = new CountDownLatch( 1 );
-
- /**
- * The latch for closeBlocking()
- */
- private CountDownLatch closeLatch = new CountDownLatch( 1 );
-
- /**
- * The socket timeout value to be used in milliseconds.
- */
- private int connectTimeout = 0;
-
- /**
- * Constructs a WebSocketClient instance and sets it to the connect to the
- * specified URI. The channel does not attampt to connect automatically. The connection
- * will be established once you call connect.
- *
- * @param serverUri the server URI to connect to
- */
- public WebSocketClient( URI serverUri ) {
- this( serverUri, new Draft_6455());
- }
-
- /**
- * Constructs a WebSocketClient instance and sets it to the connect to the
- * specified URI. The channel does not attampt to connect automatically. The connection
- * will be established once you call connect.
- * @param serverUri the server URI to connect to
- * @param protocolDraft The draft which should be used for this connection
- */
- public WebSocketClient( URI serverUri , Draft protocolDraft ) {
- this( serverUri, protocolDraft, null, 0 );
- }
-
- /**
- * Constructs a WebSocketClient instance and sets it to the connect to the
- * specified URI. The channel does not attampt to connect automatically. The connection
- * will be established once you call connect.
- * @param serverUri the server URI to connect to
- * @param httpHeaders Additional HTTP-Headers
- * @since 1.3.8
- */
- public WebSocketClient( URI serverUri, Map httpHeaders) {
- this(serverUri, new Draft_6455(), httpHeaders);
- }
-
- /**
- * Constructs a WebSocketClient instance and sets it to the connect to the
- * specified URI. The channel does not attampt to connect automatically. The connection
- * will be established once you call connect.
- * @param serverUri the server URI to connect to
- * @param protocolDraft The draft which should be used for this connection
- * @param httpHeaders Additional HTTP-Headers
- * @since 1.3.8
- */
- public WebSocketClient( URI serverUri , Draft protocolDraft , Map httpHeaders) {
- this(serverUri, protocolDraft, httpHeaders, 0);
- }
-
- /**
- * Constructs a WebSocketClient instance and sets it to the connect to the
- * specified URI. The channel does not attampt to connect automatically. The connection
- * will be established once you call connect.
- * @param serverUri the server URI to connect to
- * @param protocolDraft The draft which should be used for this connection
- * @param httpHeaders Additional HTTP-Headers
- * @param connectTimeout The Timeout for the connection
- */
- public WebSocketClient( URI serverUri , Draft protocolDraft , Map httpHeaders , int connectTimeout ) {
- if( serverUri == null ) {
- throw new IllegalArgumentException();
- } else if( protocolDraft == null ) {
- throw new IllegalArgumentException( "null as draft is permitted for `WebSocketServer` only!" );
- }
- this.uri = serverUri;
- this.draft = protocolDraft;
- this.headers = httpHeaders;
- this.connectTimeout = connectTimeout;
- setTcpNoDelay( false );
- setReuseAddr( false );
- this.engine = new WebSocketImpl( this, protocolDraft );
- }
-
- /**
- * Returns the URI that this WebSocketClient is connected to.
- * @return the URI connected to
- */
- public URI getURI() {
- return uri;
- }
-
- /**
- * Returns the protocol version this channel uses.
- * For more infos see https://github.com/TooTallNate/Java-WebSocket/wiki/Drafts
- * @return The draft used for this client
- */
- public Draft getDraft() {
- return draft;
- }
-
- /**
- * Returns the socket to allow Hostname Verification
- * @return the socket used for this connection
- */
- public Socket getSocket() {
- return socket;
- }
-
- /**
- * Reinitiates the websocket connection. This method does not block.
- * @since 1.3.8
- */
- public void reconnect() {
- reset();
- connect();
- }
-
- /**
- * Same as reconnect but blocks until the websocket reconnected or failed to do so.
- * @return Returns whether it succeeded or not.
- * @throws InterruptedException Thrown when the threads get interrupted
- * @since 1.3.8
- */
- public boolean reconnectBlocking() throws InterruptedException {
- reset();
- return connectBlocking();
- }
-
- /**
- * Reset everything relevant to allow a reconnect
- * @since 1.3.8
- */
- private void reset() {
- try {
- closeBlocking();
- if( writeThread != null ) {
- this.writeThread.interrupt();
- this.writeThread = null;
- }
- this.draft.reset();
- if( this.socket != null ) {
- this.socket.close();
- this.socket = null;
- }
- } catch ( Exception e ) {
- onError( e );
- engine.closeConnection( CloseFrame.ABNORMAL_CLOSE, e.getMessage() );
- return;
- }
- connectLatch = new CountDownLatch( 1 );
- closeLatch = new CountDownLatch( 1 );
- this.engine = new WebSocketImpl( this, this.draft );
- }
-
- /**
- * Initiates the websocket connection. This method does not block.
- */
- public void connect() {
- if( writeThread != null )
- throw new IllegalStateException( "WebSocketClient objects are not reuseable" );
- writeThread = new Thread( this );
- writeThread.setName( "WebSocketConnectReadThread-" + writeThread.getId() );
- writeThread.start();
- }
-
- /**
- * Same as connect but blocks until the websocket connected or failed to do so.
- * @return Returns whether it succeeded or not.
- * @throws InterruptedException Thrown when the threads get interrupted
- */
- public boolean connectBlocking() throws InterruptedException {
- connect();
- connectLatch.await();
- return engine.isOpen();
- }
-
- /**
- * Initiates the websocket close handshake. This method does not block
- * In oder to make sure the connection is closed use closeBlocking
- */
- public void close() {
- if( writeThread != null ) {
- engine.close( CloseFrame.NORMAL );
- }
- }
- /**
- * Same as close but blocks until the websocket closed or failed to do so.
- * @throws InterruptedException Thrown when the threads get interrupted
- */
- public void closeBlocking() throws InterruptedException {
- close();
- closeLatch.await();
- }
-
- /**
- * Sends text to the connected websocket server.
- *
- * @param text
- * The string which will be transmitted.
- */
- public void send( String text ) throws NotYetConnectedException {
- engine.send( text );
- }
-
- /**
- * Sends binary data to the connected webSocket server.
- *
- * @param data
- * The byte-Array of data to send to the WebSocket server.
- */
- public void send( byte[] data ) throws NotYetConnectedException {
- engine.send( data );
- }
-
- @Override
- public T getAttachment() {
- return engine.getAttachment();
- }
-
- @Override
- public void setAttachment(T attachment) {
- engine.setAttachment( attachment );
- }
-
- @Override
- protected Collection getConnections() {
- return Collections.singletonList((WebSocket ) engine );
- }
-
- @Override
- public void sendPing() throws NotYetConnectedException {
- engine.sendPing( );
- }
-
- public void run() {
- InputStream istream;
- try {
- boolean isNewSocket = false;
-
- if( socket == null ) {
- socket = new Socket( proxy );
- isNewSocket = true;
-
- } else if( socket.isClosed() ) {
- throw new IOException();
- }
-
- socket.setTcpNoDelay( isTcpNoDelay() );
- socket.setReuseAddress( isReuseAddr() );
-
- if( !socket.isBound() ) {
- socket.connect( new InetSocketAddress( uri.getHost(), getPort() ), connectTimeout );
- }
-
- // if the socket is set by others we don't apply any TLS wrapper
- if (isNewSocket && "wss".equals( uri.getScheme())) {
-
- SSLContext sslContext = SSLContext.getInstance("TLS");
- sslContext.init(null, null, null);
- SSLSocketFactory factory = sslContext.getSocketFactory();
- socket = factory.createSocket(socket, uri.getHost(), getPort(), true);
- }
-
- istream = socket.getInputStream();
- ostream = socket.getOutputStream();
-
- sendHandshake();
- } catch ( /*IOException | SecurityException | UnresolvedAddressException | InvalidHandshakeException | ClosedByInterruptException | SocketTimeoutException */Exception e ) {
- onWebsocketError( engine, e );
- engine.closeConnection( CloseFrame.NEVER_CONNECTED, e.getMessage() );
- return;
- }
-
- writeThread = new Thread( new WebsocketWriteThread() );
- writeThread.start();
-
- byte[] rawbuffer = new byte[ WebSocketImpl.RCVBUF ];
- int readBytes;
-
- try {
- while ( !isClosing() && !isClosed() && ( readBytes = istream.read( rawbuffer ) ) != -1 ) {
- engine.decode( ByteBuffer.wrap( rawbuffer, 0, readBytes ) );
- }
- engine.eot();
- } catch ( IOException e ) {
- handleIOException(e);
- } catch ( RuntimeException e ) {
- // this catch case covers internal errors only and indicates a bug in this websocket implementation
- onError( e );
- engine.closeConnection( CloseFrame.ABNORMAL_CLOSE, e.getMessage() );
- }
- //I have no idea why this was added.
- //assert ( socket.isClosed() );
- }
-
- /**
- * Extract the specified port
- * @return the specified port or the default port for the specific scheme
- */
- private int getPort() {
- int port = uri.getPort();
- if( port == -1 ) {
- String scheme = uri.getScheme();
- if( "wss".equals( scheme ) ) {
- return WebSocket.DEFAULT_WSS_PORT;
- } else if( "ws".equals( scheme ) ) {
- return WebSocket.DEFAULT_PORT;
- } else {
- throw new IllegalArgumentException( "unknown scheme: " + scheme );
- }
- }
- return port;
- }
-
- /**
- * Create and send the handshake to the other endpoint
- * @throws InvalidHandshakeException a invalid handshake was created
- */
- private void sendHandshake() throws InvalidHandshakeException {
- String path;
- String part1 = uri.getRawPath();
- String part2 = uri.getRawQuery();
- if( part1 == null || part1.length() == 0 )
- path = "/";
- else
- path = part1;
- if( part2 != null )
- path += '?' + part2;
- int port = getPort();
- String host = uri.getHost() + (
- (port != WebSocket.DEFAULT_PORT && port != WebSocket.DEFAULT_WSS_PORT)
- ? ":" + port
- : "" );
-
- HandshakeImpl1Client handshake = new HandshakeImpl1Client();
- handshake.setResourceDescriptor( path );
- handshake.put( "Host", host );
- if( headers != null ) {
- for( Map.Entry kv : headers.entrySet() ) {
- handshake.put( kv.getKey(), kv.getValue() );
- }
- }
- engine.startHandshake( handshake );
- }
-
- /**
- * This represents the state of the connection.
- */
- public READYSTATE getReadyState() {
- return engine.getReadyState();
- }
-
- /**
- * Calls subclass' implementation of onMessage.
- */
- @Override
- public final void onWebsocketMessage( WebSocket conn, String message ) {
- onMessage( message );
- }
-
- @Override
- public final void onWebsocketMessage( WebSocket conn, ByteBuffer blob ) {
- onMessage( blob );
- }
-
- @Override
- public void onWebsocketMessageFragment( WebSocket conn, Framedata frame ) {
- onFragment( frame );
- }
-
- /**
- * Calls subclass' implementation of onOpen.
- */
- @Override
- public final void onWebsocketOpen( WebSocket conn, Handshakedata handshake ) {
- startConnectionLostTimer();
- onOpen( (ServerHandshake) handshake );
- connectLatch.countDown();
- }
-
- /**
- * Calls subclass' implementation of onClose.
- */
- @Override
- public final void onWebsocketClose( WebSocket conn, int code, String reason, boolean remote ) {
- stopConnectionLostTimer();
- if( writeThread != null )
- writeThread.interrupt();
- onClose( code, reason, remote );
- connectLatch.countDown();
- closeLatch.countDown();
- }
-
- /**
- * Calls subclass' implementation of onIOError.
- */
- @Override
- public final void onWebsocketError( WebSocket conn, Exception ex ) {
- onError( ex );
- }
-
- @Override
- public final void onWriteDemand( WebSocket conn ) {
- // nothing to do
- }
-
- @Override
- public void onWebsocketCloseInitiated( WebSocket conn, int code, String reason ) {
- onCloseInitiated( code, reason );
- }
-
- @Override
- public void onWebsocketClosing( WebSocket conn, int code, String reason, boolean remote ) {
- onClosing( code, reason, remote );
- }
-
- /**
- * Send when this peer sends a close handshake
- *
- * @param code The codes can be looked up here: {@link CloseFrame}
- * @param reason Additional information string
- */
- public void onCloseInitiated( int code, String reason ) {
- //To overwrite
- }
-
- /** Called as soon as no further frames are accepted
- *
- * @param code The codes can be looked up here: {@link CloseFrame}
- * @param reason Additional information string
- * @param remote Returns whether or not the closing of the connection was initiated by the remote host.
- */
- public void onClosing( int code, String reason, boolean remote ) {
- //To overwrite
- }
-
- /**
- * Getter for the engine
- * @return the engine
- */
- public WebSocket getConnection() {
- return engine;
- }
-
- @Override
- public InetSocketAddress getLocalSocketAddress( WebSocket conn ) {
- if( socket != null )
- return (InetSocketAddress) socket.getLocalSocketAddress();
- return null;
- }
-
- @Override
- public InetSocketAddress getRemoteSocketAddress( WebSocket conn ) {
- if( socket != null )
- return (InetSocketAddress) socket.getRemoteSocketAddress();
- return null;
- }
-
- // ABTRACT METHODS /////////////////////////////////////////////////////////
-
- /**
- * Called after an opening handshake has been performed and the given websocket is ready to be written on.
- * @param handshakedata The handshake of the websocket instance
- */
- public abstract void onOpen( ServerHandshake handshakedata );
-
- /**
- * Callback for string messages received from the remote host
- *
- * @see #onMessage(ByteBuffer)
- * @param message The UTF-8 decoded message that was received.
- **/
- public abstract void onMessage( String message );
-
- /**
- * Called after the websocket connection has been closed.
- *
- * @param code
- * The codes can be looked up here: {@link CloseFrame}
- * @param reason
- * Additional information string
- * @param remote
- * Returns whether or not the closing of the connection was initiated by the remote host.
- **/
- public abstract void onClose( int code, String reason, boolean remote );
-
- /**
- * Called when errors occurs. If an error causes the websocket connection to fail {@link #onClose(int, String, boolean)} will be called additionally.
- * This method will be called primarily because of IO or protocol errors.
- * If the given exception is an RuntimeException that probably means that you encountered a bug.
- *
- * @param ex The exception causing this error
- **/
- public abstract void onError( Exception ex );
-
- /**
- * Callback for binary messages received from the remote host
- *
- * @see #onMessage(String)
- *
- * @param bytes
- * The binary message that was received.
- **/
- public void onMessage( ByteBuffer bytes ) {
- //To overwrite
- }
-
- /**
- * Callback for fragmented frames
- * @see WebSocket#sendFragmentedFrame(org.java_websocket.framing.Framedata.Opcode, ByteBuffer, boolean)
- * @param frame The fragmented frame
- */
- @Deprecated
- public void onFragment( Framedata frame ) {
- //To overwrite
- }
-
- private class WebsocketWriteThread implements Runnable {
- @Override
- public void run() {
- Thread.currentThread().setName( "WebSocketWriteThread-" + Thread.currentThread().getId() );
- try {
- try {
- while( !Thread.interrupted() ) {
- ByteBuffer buffer = engine.outQueue.take();
- ostream.write( buffer.array(), 0, buffer.limit() );
- ostream.flush();
- }
- } catch ( InterruptedException e ) {
- for (ByteBuffer buffer : engine.outQueue) {
- ostream.write( buffer.array(), 0, buffer.limit() );
- ostream.flush();
- }
- }
- } catch ( IOException e ) {
- handleIOException( e );
- } finally {
- closeSocket();
- writeThread = null;
- }
- }
- }
-
- /**
- * Closing the socket
- */
- private void closeSocket() {
- try {
- if( socket != null ) {
- socket.close();
- }
- } catch ( IOException ex ) {
- onWebsocketError( this, ex );
- }
- }
-
- /**
- * Method to set a proxy for this connection
- * @param proxy the proxy to use for this websocket client
- */
- public void setProxy( Proxy proxy ) {
- if( proxy == null )
- throw new IllegalArgumentException();
- this.proxy = proxy;
- }
-
- /**
- * Accepts bound and unbound sockets.
- * This method must be called before connect.
- * If the given socket is not yet bound it will be bound to the uri specified in the constructor.
- * @param socket The socket which should be used for the connection
- */
- public void setSocket( Socket socket ) {
- if( this.socket != null ) {
- throw new IllegalStateException( "socket has already been set" );
- }
- this.socket = socket;
- }
-
- @Override
- public void sendFragmentedFrame( Opcode op, ByteBuffer buffer, boolean fin ) {
- engine.sendFragmentedFrame( op, buffer, fin );
- }
-
- @Override
- public boolean isOpen() {
- return engine.isOpen();
- }
-
- @Override
- public boolean isFlushAndClose() {
- return engine.isFlushAndClose();
- }
-
- @Override
- public boolean isClosed() {
- return engine.isClosed();
- }
-
- @Override
- public boolean isClosing() {
- return engine.isClosing();
- }
-
- @Override
- @Deprecated
- public boolean isConnecting() {
- return engine.isConnecting();
- }
-
- @Override
- public boolean hasBufferedData() {
- return engine.hasBufferedData();
- }
-
- @Override
- public void close( int code ) {
- engine.close();
- }
-
- @Override
- public void close( int code, String message ) {
- engine.close( code, message );
- }
-
- @Override
- public void closeConnection( int code, String message ) {
- engine.closeConnection( code, message );
- }
-
- @Override
- public void send( ByteBuffer bytes ) throws IllegalArgumentException , NotYetConnectedException {
- engine.send( bytes );
- }
-
- @Override
- public void sendFrame( Framedata framedata ) {
- engine.sendFrame( framedata );
- }
-
- @Override
- public void sendFrame( Collection frames ) {
- engine.sendFrame( frames );
- }
-
- @Override
- public InetSocketAddress getLocalSocketAddress() {
- return engine.getLocalSocketAddress();
- }
- @Override
- public InetSocketAddress getRemoteSocketAddress() {
- return engine.getRemoteSocketAddress();
- }
-
- @Override
- public String getResourceDescriptor() {
- return uri.getPath();
- }
-
-
- /**
- * Method to give some additional info for specific IOExceptions
- * @param e the IOException causing a eot.
- */
- private void handleIOException( IOException e ) {
- if (e instanceof SSLException) {
- onError( e );
- }
- engine.eot();
- }
+ /**
+ * The URI this channel is supposed to connect to.
+ */
+ protected URI uri = null;
+
+ /**
+ * The underlying engine
+ */
+ private WebSocketImpl engine = null;
+
+ /**
+ * The socket for this WebSocketClient
+ */
+ private Socket socket = null;
+
+ /**
+ * The SocketFactory for this WebSocketClient
+ *
+ * @since 1.4.0
+ */
+ private SocketFactory socketFactory = null;
+
+ /**
+ * The used OutputStream
+ */
+ private OutputStream ostream;
+
+ /**
+ * The used proxy, if any
+ */
+ private Proxy proxy = Proxy.NO_PROXY;
+
+ /**
+ * The thread to write outgoing message
+ */
+ private Thread writeThread;
+
+ /**
+ * The thread to connect and read message
+ */
+ private Thread connectReadThread;
+
+ /**
+ * The draft to use
+ */
+ private Draft draft;
+
+ /**
+ * The additional headers to use
+ */
+ private Map headers;
+
+ /**
+ * The latch for connectBlocking()
+ */
+ private CountDownLatch connectLatch = new CountDownLatch(1);
+
+ /**
+ * The latch for closeBlocking()
+ */
+ private CountDownLatch closeLatch = new CountDownLatch(1);
+
+ /**
+ * The socket timeout value to be used in milliseconds.
+ */
+ private int connectTimeout = 0;
+
+ /**
+ * DNS resolver that translates a URI to an InetAddress
+ *
+ * @see InetAddress
+ * @since 1.4.1
+ */
+ private DnsResolver dnsResolver = null;
+
+ /**
+ * Constructs a WebSocketClient instance and sets it to the connect to the specified URI. The
+ * channel does not attampt to connect automatically. The connection will be established once you
+ * call connect.
+ *
+ * @param serverUri the server URI to connect to
+ */
+ public WebSocketClient(URI serverUri) {
+ this(serverUri, new Draft_6455());
+ }
+
+ /**
+ * Constructs a WebSocketClient instance and sets it to the connect to the specified URI. The
+ * channel does not attampt to connect automatically. The connection will be established once you
+ * call connect.
+ *
+ * @param serverUri the server URI to connect to
+ * @param protocolDraft The draft which should be used for this connection
+ */
+ public WebSocketClient(URI serverUri, Draft protocolDraft) {
+ this(serverUri, protocolDraft, null, 0);
+ }
+
+ /**
+ * Constructs a WebSocketClient instance and sets it to the connect to the specified URI. The
+ * channel does not attampt to connect automatically. The connection will be established once you
+ * call connect.
+ *
+ * @param serverUri the server URI to connect to
+ * @param httpHeaders Additional HTTP-Headers
+ * @since 1.3.8
+ */
+ public WebSocketClient(URI serverUri, Map httpHeaders) {
+ this(serverUri, new Draft_6455(), httpHeaders);
+ }
+
+ /**
+ * Constructs a WebSocketClient instance and sets it to the connect to the specified URI. The
+ * channel does not attampt to connect automatically. The connection will be established once you
+ * call connect.
+ *
+ * @param serverUri the server URI to connect to
+ * @param protocolDraft The draft which should be used for this connection
+ * @param httpHeaders Additional HTTP-Headers
+ * @since 1.3.8
+ */
+ public WebSocketClient(URI serverUri, Draft protocolDraft, Map httpHeaders) {
+ this(serverUri, protocolDraft, httpHeaders, 0);
+ }
+
+ /**
+ * Constructs a WebSocketClient instance and sets it to the connect to the specified URI. The
+ * channel does not attampt to connect automatically. The connection will be established once you
+ * call connect.
+ *
+ * @param serverUri the server URI to connect to
+ * @param protocolDraft The draft which should be used for this connection
+ * @param httpHeaders Additional HTTP-Headers
+ * @param connectTimeout The Timeout for the connection
+ */
+ public WebSocketClient(URI serverUri, Draft protocolDraft, Map httpHeaders,
+ int connectTimeout) {
+ if (serverUri == null) {
+ throw new IllegalArgumentException();
+ } else if (protocolDraft == null) {
+ throw new IllegalArgumentException("null as draft is permitted for `WebSocketServer` only!");
+ }
+ this.uri = serverUri;
+ this.draft = protocolDraft;
+ this.dnsResolver = new DnsResolver() {
+ @Override
+ public InetAddress resolve(URI uri) throws UnknownHostException {
+ return InetAddress.getByName(uri.getHost());
+ }
+ };
+ if (httpHeaders != null) {
+ headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ headers.putAll(httpHeaders);
+ }
+ this.connectTimeout = connectTimeout;
+ setTcpNoDelay(false);
+ setReuseAddr(false);
+ this.engine = new WebSocketImpl(this, protocolDraft);
+ }
+
+ /**
+ * Returns the URI that this WebSocketClient is connected to.
+ *
+ * @return the URI connected to
+ */
+ public URI getURI() {
+ return uri;
+ }
+
+ /**
+ * Returns the protocol version this channel uses.
For more infos see
+ * https://github.com/TooTallNate/Java-WebSocket/wiki/Drafts
+ *
+ * @return The draft used for this client
+ */
+ public Draft getDraft() {
+ return draft;
+ }
+
+ /**
+ * Returns the socket to allow Hostname Verification
+ *
+ * @return the socket used for this connection
+ */
+ public Socket getSocket() {
+ return socket;
+ }
+
+ /**
+ * @param key Name of the header to add.
+ * @param value Value of the header to add.
+ * @since 1.4.1 Adds an additional header to be sent in the handshake.
If the connection is
+ * already made, adding headers has no effect, unless reconnect is called, which then a new
+ * handshake is sent.
If a header with the same key already exists, it is overridden.
+ */
+ public void addHeader(String key, String value) {
+ if (headers == null) {
+ headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ }
+ headers.put(key, value);
+ }
+
+ /**
+ * @param key Name of the header to remove.
+ * @return the previous value associated with key, or null if there was no mapping for key.
+ * @since 1.4.1 Removes a header from the handshake to be sent, if header key exists.
+ */
+ public String removeHeader(String key) {
+ if (headers == null) {
+ return null;
+ }
+ return headers.remove(key);
+ }
+
+ /**
+ * @since 1.4.1 Clears all previously put headers.
+ */
+ public void clearHeaders() {
+ headers = null;
+ }
+
+ /**
+ * Sets a custom DNS resolver.
+ *
+ * @param dnsResolver The DnsResolver to use.
+ * @since 1.4.1
+ */
+ public void setDnsResolver(DnsResolver dnsResolver) {
+ this.dnsResolver = dnsResolver;
+ }
+
+ /**
+ * Reinitiates the websocket connection. This method does not block.
+ *
+ * @since 1.3.8
+ */
+ public void reconnect() {
+ reset();
+ connect();
+ }
+
+ /**
+ * Same as reconnect but blocks until the websocket reconnected or failed to do
+ * so.
+ *
+ * @return Returns whether it succeeded or not.
+ * @throws InterruptedException Thrown when the threads get interrupted
+ * @since 1.3.8
+ */
+ public boolean reconnectBlocking() throws InterruptedException {
+ reset();
+ return connectBlocking();
+ }
+
+ /**
+ * Same as reconnect but blocks with a timeout until the websocket connected or failed
+ * to do so.
+ *
+ * @param timeout The connect timeout
+ * @param timeUnit The timeout time unit
+ * @return Returns whether it succeeded or not.
+ * @throws InterruptedException Thrown when the threads get interrupted
+ * @since 1.6.1
+ */
+ public boolean reconnectBlocking(long timeout, TimeUnit timeUnit) throws InterruptedException {
+ reset();
+ return connectBlocking(timeout, timeUnit);
+ }
+
+ /**
+ * Reset everything relevant to allow a reconnect
+ *
+ * @since 1.3.8
+ */
+ private void reset() {
+ Thread current = Thread.currentThread();
+ if (current == writeThread || current == connectReadThread) {
+ throw new IllegalStateException(
+ "You cannot initialize a reconnect out of the websocket thread. Use reconnect in another thread to ensure a successful cleanup.");
+ }
+ try {
+ // This socket null check ensures we can reconnect a socket that failed to connect. It's an uncommon edge case, but we want to make sure we support it
+ if (engine.getReadyState() == ReadyState.NOT_YET_CONNECTED && socket != null) {
+ // Closing the socket when we have not connected prevents the writeThread from hanging on a write indefinitely during connection teardown
+ socket.close();
+ }
+ closeBlocking();
+
+ if (writeThread != null) {
+ this.writeThread.interrupt();
+ this.writeThread.join();
+ this.writeThread = null;
+ }
+ if (connectReadThread != null) {
+ this.connectReadThread.interrupt();
+ this.connectReadThread.join();
+ this.connectReadThread = null;
+ }
+ this.draft.reset();
+ if (this.socket != null) {
+ this.socket.close();
+ this.socket = null;
+ }
+ } catch (Exception e) {
+ onError(e);
+ engine.closeConnection(CloseFrame.ABNORMAL_CLOSE, e.getMessage());
+ return;
+ }
+ connectLatch = new CountDownLatch(1);
+ closeLatch = new CountDownLatch(1);
+ this.engine = new WebSocketImpl(this, this.draft);
+ }
+
+ /**
+ * Initiates the websocket connection. This method does not block.
+ */
+ public void connect() {
+ if (connectReadThread != null) {
+ throw new IllegalStateException("WebSocketClient objects are not reuseable");
+ }
+ connectReadThread = new Thread(this);
+ connectReadThread.setDaemon(isDaemon());
+ connectReadThread.setName("WebSocketConnectReadThread-" + connectReadThread.getId());
+ connectReadThread.start();
+ }
+
+ /**
+ * Same as connect but blocks until the websocket connected or failed to do so.
+ *
+ * @return Returns whether it succeeded or not.
+ * @throws InterruptedException Thrown when the threads get interrupted
+ */
+ public boolean connectBlocking() throws InterruptedException {
+ connect();
+ connectLatch.await();
+ return engine.isOpen();
+ }
+
+ /**
+ * Same as connect but blocks with a timeout until the websocket connected or failed
+ * to do so.
+ *
+ * @param timeout The connect timeout
+ * @param timeUnit The timeout time unit
+ * @return Returns whether it succeeded or not.
+ * @throws InterruptedException Thrown when the threads get interrupted
+ */
+ public boolean connectBlocking(long timeout, TimeUnit timeUnit) throws InterruptedException {
+ connect();
+
+ boolean connected = connectLatch.await(timeout, timeUnit);
+ if (!connected) {
+ reset();
+ }
+
+ return connected && engine.isOpen();
+ }
+
+ /**
+ * Initiates the websocket close handshake. This method does not block
In oder to make sure
+ * the connection is closed use closeBlocking
+ */
+ public void close() {
+ if (writeThread != null) {
+ engine.close(CloseFrame.NORMAL);
+ }
+ }
+
+ /**
+ * Same as close but blocks until the websocket closed or failed to do so.
+ *
+ * @throws InterruptedException Thrown when the threads get interrupted
+ */
+ public void closeBlocking() throws InterruptedException {
+ close();
+ closeLatch.await();
+ }
+
+ /**
+ * Sends text to the connected websocket server.
+ *
+ * @param text The string which will be transmitted.
+ */
+ public void send(String text) {
+ engine.send(text);
+ }
+
+ /**
+ * Sends binary data to the connected webSocket server.
+ *
+ * @param data The byte-Array of data to send to the WebSocket server.
+ */
+ public void send(byte[] data) {
+ engine.send(data);
+ }
+
+ @Override
+ public T getAttachment() {
+ return engine.getAttachment();
+ }
+
+ @Override
+ public void setAttachment(T attachment) {
+ engine.setAttachment(attachment);
+ }
+
+ @Override
+ protected Collection getConnections() {
+ return Collections.singletonList((WebSocket) engine);
+ }
+
+ @Override
+ public void sendPing() {
+ engine.sendPing();
+ }
+
+ public void run() {
+ InputStream istream;
+ try {
+ boolean upgradeSocketToSSLSocket = prepareSocket();
+
+ socket.setTcpNoDelay(isTcpNoDelay());
+ socket.setReuseAddress(isReuseAddr());
+ int receiveBufferSize = getReceiveBufferSize();
+ if (receiveBufferSize > 0) {
+ socket.setReceiveBufferSize(receiveBufferSize);
+ }
+
+ if (!socket.isConnected()) {
+ InetSocketAddress addr = dnsResolver == null ? InetSocketAddress.createUnresolved(uri.getHost(), getPort()) : new InetSocketAddress(dnsResolver.resolve(uri), this.getPort());
+ socket.connect(addr, connectTimeout);
+ }
+
+ // if the socket is set by others we don't apply any TLS wrapper
+ if (upgradeSocketToSSLSocket && "wss".equals(uri.getScheme())) {
+ upgradeSocketToSSL();
+ }
+
+ if (socket instanceof SSLSocket) {
+ SSLSocket sslSocket = (SSLSocket) socket;
+ SSLParameters sslParameters = sslSocket.getSSLParameters();
+ onSetSSLParameters(sslParameters);
+ sslSocket.setSSLParameters(sslParameters);
+ }
+
+ istream = socket.getInputStream();
+ ostream = socket.getOutputStream();
+
+ sendHandshake();
+ } catch (/*IOException | SecurityException | UnresolvedAddressException | InvalidHandshakeException | ClosedByInterruptException | SocketTimeoutException */Exception e) {
+ onWebsocketError(engine, e);
+ engine.closeConnection(CloseFrame.NEVER_CONNECTED, e.getMessage());
+ return;
+ } catch (InternalError e) {
+ // https://bugs.openjdk.java.net/browse/JDK-8173620
+ if (e.getCause() instanceof InvocationTargetException && e.getCause()
+ .getCause() instanceof IOException) {
+ IOException cause = (IOException) e.getCause().getCause();
+ onWebsocketError(engine, cause);
+ engine.closeConnection(CloseFrame.NEVER_CONNECTED, cause.getMessage());
+ return;
+ }
+ throw e;
+ }
+
+ if (writeThread != null) {
+ writeThread.interrupt();
+ try {
+ writeThread.join();
+ } catch (InterruptedException e) {
+ /* ignore */
+ }
+ }
+ writeThread = new Thread(new WebsocketWriteThread(this));
+ writeThread.setDaemon(isDaemon());
+ writeThread.start();
+
+ int receiveBufferSize = getReceiveBufferSize();
+ byte[] rawbuffer = new byte[receiveBufferSize > 0 ? receiveBufferSize : DEFAULT_READ_BUFFER_SIZE];
+ int readBytes;
+
+ try {
+ while (!isClosing() && !isClosed() && (readBytes = istream.read(rawbuffer)) != -1) {
+ engine.decode(ByteBuffer.wrap(rawbuffer, 0, readBytes));
+ }
+ engine.eot();
+ } catch (IOException e) {
+ handleIOException(e);
+ } catch (RuntimeException e) {
+ // this catch case covers internal errors only and indicates a bug in this websocket implementation
+ onError(e);
+ engine.closeConnection(CloseFrame.ABNORMAL_CLOSE, e.getMessage());
+ }
+ }
+
+ private void upgradeSocketToSSL()
+ throws NoSuchAlgorithmException, KeyManagementException, IOException {
+ SSLSocketFactory factory;
+ // Prioritise the provided socketfactory
+ // Helps when using web debuggers like Fiddler Classic
+ if (socketFactory instanceof SSLSocketFactory) {
+ factory = (SSLSocketFactory) socketFactory;
+ } else {
+ factory = (SSLSocketFactory) SSLSocketFactory.getDefault();
+ }
+ socket = factory.createSocket(socket, uri.getHost(), getPort(), true);
+ }
+
+ private boolean prepareSocket() throws IOException {
+ boolean upgradeSocketToSSLSocket = false;
+ // Prioritise a proxy over a socket factory and apply the socketfactory later
+ if (proxy != Proxy.NO_PROXY) {
+ socket = new Socket(proxy);
+ upgradeSocketToSSLSocket = true;
+ } else if (socketFactory != null) {
+ socket = socketFactory.createSocket();
+ } else if (socket == null) {
+ socket = new Socket(proxy);
+ upgradeSocketToSSLSocket = true;
+ } else if (socket.isClosed()) {
+ throw new IOException();
+ }
+ return upgradeSocketToSSLSocket;
+ }
+
+ /**
+ * Apply specific SSLParameters If you override this method make sure to always call
+ * super.onSetSSLParameters() to ensure the hostname validation is active
+ *
+ * @param sslParameters the SSLParameters which will be used for the SSLSocket
+ */
+ protected void onSetSSLParameters(SSLParameters sslParameters) {
+ // If you run into problem on Android (NoSuchMethodException), check out the wiki https://github.com/TooTallNate/Java-WebSocket/wiki/No-such-method-error-setEndpointIdentificationAlgorithm
+ // Perform hostname validation
+ sslParameters.setEndpointIdentificationAlgorithm("HTTPS");
+ }
+
+ /**
+ * Extract the specified port
+ *
+ * @return the specified port or the default port for the specific scheme
+ */
+ private int getPort() {
+ int port = uri.getPort();
+ String scheme = uri.getScheme();
+ if ("wss".equals(scheme)) {
+ return port == -1 ? WebSocketImpl.DEFAULT_WSS_PORT : port;
+ } else if ("ws".equals(scheme)) {
+ return port == -1 ? WebSocketImpl.DEFAULT_PORT : port;
+ } else {
+ throw new IllegalArgumentException("unknown scheme: " + scheme);
+ }
+ }
+
+ /**
+ * Create and send the handshake to the other endpoint
+ *
+ * @throws InvalidHandshakeException a invalid handshake was created
+ */
+ private void sendHandshake() throws InvalidHandshakeException {
+ String path;
+ String part1 = uri.getRawPath();
+ String part2 = uri.getRawQuery();
+ if (part1 == null || part1.length() == 0) {
+ path = "/";
+ } else {
+ path = part1;
+ }
+ if (part2 != null) {
+ path += '?' + part2;
+ }
+ int port = getPort();
+ String host = uri.getHost() + (
+ (port != WebSocketImpl.DEFAULT_PORT && port != WebSocketImpl.DEFAULT_WSS_PORT)
+ ? ":" + port
+ : "");
+
+ HandshakeImpl1Client handshake = new HandshakeImpl1Client();
+ handshake.setResourceDescriptor(path);
+ handshake.put("Host", host);
+ if (headers != null) {
+ for (Map.Entry kv : headers.entrySet()) {
+ handshake.put(kv.getKey(), kv.getValue());
+ }
+ }
+ engine.startHandshake(handshake);
+ }
+
+ /**
+ * This represents the state of the connection.
+ */
+ public ReadyState getReadyState() {
+ return engine.getReadyState();
+ }
+
+ /**
+ * Calls subclass' implementation of onMessage.
+ */
+ @Override
+ public final void onWebsocketMessage(WebSocket conn, String message) {
+ onMessage(message);
+ }
+
+ @Override
+ public final void onWebsocketMessage(WebSocket conn, ByteBuffer blob) {
+ onMessage(blob);
+ }
+
+ /**
+ * Calls subclass' implementation of onOpen.
+ */
+ @Override
+ public final void onWebsocketOpen(WebSocket conn, Handshakedata handshake) {
+ startConnectionLostTimer();
+ onOpen((ServerHandshake) handshake);
+ connectLatch.countDown();
+ }
+
+ /**
+ * Calls subclass' implementation of onClose.
+ */
+ @Override
+ public final void onWebsocketClose(WebSocket conn, int code, String reason, boolean remote) {
+ stopConnectionLostTimer();
+ if (writeThread != null) {
+ writeThread.interrupt();
+ }
+ onClose(code, reason, remote);
+ connectLatch.countDown();
+ closeLatch.countDown();
+ }
+
+ /**
+ * Calls subclass' implementation of onIOError.
+ */
+ @Override
+ public final void onWebsocketError(WebSocket conn, Exception ex) {
+ onError(ex);
+ }
+
+ @Override
+ public final void onWriteDemand(WebSocket conn) {
+ // nothing to do
+ }
+
+ @Override
+ public void onWebsocketCloseInitiated(WebSocket conn, int code, String reason) {
+ onCloseInitiated(code, reason);
+ }
+
+ @Override
+ public void onWebsocketClosing(WebSocket conn, int code, String reason, boolean remote) {
+ onClosing(code, reason, remote);
+ }
+
+ /**
+ * Send when this peer sends a close handshake
+ *
+ * @param code The codes can be looked up here: {@link CloseFrame}
+ * @param reason Additional information string
+ */
+ public void onCloseInitiated(int code, String reason) {
+ //To overwrite
+ }
+
+ /**
+ * Called as soon as no further frames are accepted
+ *
+ * @param code The codes can be looked up here: {@link CloseFrame}
+ * @param reason Additional information string
+ * @param remote Returns whether or not the closing of the connection was initiated by the remote
+ * host.
+ */
+ public void onClosing(int code, String reason, boolean remote) {
+ //To overwrite
+ }
+
+ /**
+ * Getter for the engine
+ *
+ * @return the engine
+ */
+ public WebSocket getConnection() {
+ return engine;
+ }
+
+ @Override
+ public InetSocketAddress getLocalSocketAddress(WebSocket conn) {
+ if (socket != null) {
+ return (InetSocketAddress) socket.getLocalSocketAddress();
+ }
+ return null;
+ }
+
+ @Override
+ public InetSocketAddress getRemoteSocketAddress(WebSocket conn) {
+ if (socket != null) {
+ return (InetSocketAddress) socket.getRemoteSocketAddress();
+ }
+ return null;
+ }
+
+ // ABSTRACT METHODS /////////////////////////////////////////////////////////
+
+ /**
+ * Called after an opening handshake has been performed and the given websocket is ready to be
+ * written on.
+ *
+ * @param handshakedata The handshake of the websocket instance
+ */
+ public abstract void onOpen(ServerHandshake handshakedata);
+
+ /**
+ * Callback for string messages received from the remote host
+ *
+ * @param message The UTF-8 decoded message that was received.
+ * @see #onMessage(ByteBuffer)
+ **/
+ public abstract void onMessage(String message);
+
+ /**
+ * Called after the websocket connection has been closed.
+ *
+ * @param code The codes can be looked up here: {@link CloseFrame}
+ * @param reason Additional information string
+ * @param remote Returns whether or not the closing of the connection was initiated by the remote
+ * host.
+ **/
+ public abstract void onClose(int code, String reason, boolean remote);
+
+ /**
+ * Called when errors occurs. If an error causes the websocket connection to fail {@link
+ * #onClose(int, String, boolean)} will be called additionally.
This method will be called
+ * primarily because of IO or protocol errors.
If the given exception is an RuntimeException
+ * that probably means that you encountered a bug.
+ *
+ * @param ex The exception causing this error
+ **/
+ public abstract void onError(Exception ex);
+
+ /**
+ * Callback for binary messages received from the remote host
+ *
+ * @param bytes The binary message that was received.
+ * @see #onMessage(String)
+ **/
+ public void onMessage(ByteBuffer bytes) {
+ //To overwrite
+ }
+
+
+ private class WebsocketWriteThread implements Runnable {
+
+ private final WebSocketClient webSocketClient;
+
+ WebsocketWriteThread(WebSocketClient webSocketClient) {
+ this.webSocketClient = webSocketClient;
+ }
+
+ @Override
+ public void run() {
+ Thread.currentThread().setName("WebSocketWriteThread-" + Thread.currentThread().getId());
+ try {
+ runWriteData();
+ } catch (IOException e) {
+ handleIOException(e);
+ } finally {
+ closeSocket();
+ }
+ }
+
+ /**
+ * Write the data into the outstream
+ *
+ * @throws IOException if write or flush did not work
+ */
+ private void runWriteData() throws IOException {
+ try {
+ while (!Thread.interrupted()) {
+ ByteBuffer buffer = engine.outQueue.take();
+ ostream.write(buffer.array(), 0, buffer.limit());
+ ostream.flush();
+ }
+ } catch (InterruptedException e) {
+ for (ByteBuffer buffer : engine.outQueue) {
+ ostream.write(buffer.array(), 0, buffer.limit());
+ ostream.flush();
+ }
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ /**
+ * Closing the socket
+ */
+ private void closeSocket() {
+ try {
+ if (socket != null) {
+ socket.close();
+ }
+ } catch (IOException ex) {
+ onWebsocketError(webSocketClient, ex);
+ }
+ }
+ }
+
+
+ /**
+ * Method to set a proxy for this connection
+ *
+ * @param proxy the proxy to use for this websocket client
+ */
+ public void setProxy(Proxy proxy) {
+ if (proxy == null) {
+ throw new IllegalArgumentException();
+ }
+ this.proxy = proxy;
+ }
+
+ /**
+ * Accepts bound and unbound sockets.
This method must be called before connect.
+ * If the given socket is not yet bound it will be bound to the uri specified in the constructor.
+ *
+ * @param socket The socket which should be used for the connection
+ * @deprecated use setSocketFactory
+ */
+ @Deprecated
+ public void setSocket(Socket socket) {
+ if (this.socket != null) {
+ throw new IllegalStateException("socket has already been set");
+ }
+ this.socket = socket;
+ }
+
+ /**
+ * Accepts a SocketFactory.
This method must be called before connect. The socket
+ * will be bound to the uri specified in the constructor.
+ *
+ * @param socketFactory The socket factory which should be used for the connection.
+ */
+ public void setSocketFactory(SocketFactory socketFactory) {
+ this.socketFactory = socketFactory;
+ }
+
+ @Override
+ public void sendFragmentedFrame(Opcode op, ByteBuffer buffer, boolean fin) {
+ engine.sendFragmentedFrame(op, buffer, fin);
+ }
+
+ @Override
+ public boolean isOpen() {
+ return engine.isOpen();
+ }
+
+ @Override
+ public boolean isFlushAndClose() {
+ return engine.isFlushAndClose();
+ }
+
+ @Override
+ public boolean isClosed() {
+ return engine.isClosed();
+ }
+
+ @Override
+ public boolean isClosing() {
+ return engine.isClosing();
+ }
+
+ @Override
+ public boolean hasBufferedData() {
+ return engine.hasBufferedData();
+ }
+
+ @Override
+ public void close(int code) {
+ engine.close(code);
+ }
+
+ @Override
+ public void close(int code, String message) {
+ engine.close(code, message);
+ }
+
+ @Override
+ public void closeConnection(int code, String message) {
+ engine.closeConnection(code, message);
+ }
+
+ @Override
+ public void send(ByteBuffer bytes) {
+ engine.send(bytes);
+ }
+
+ @Override
+ public void sendFrame(Framedata framedata) {
+ engine.sendFrame(framedata);
+ }
+
+ @Override
+ public void sendFrame(Collection frames) {
+ engine.sendFrame(frames);
+ }
+
+ @Override
+ public InetSocketAddress getLocalSocketAddress() {
+ return engine.getLocalSocketAddress();
+ }
+
+ @Override
+ public InetSocketAddress getRemoteSocketAddress() {
+ return engine.getRemoteSocketAddress();
+ }
+
+ @Override
+ public String getResourceDescriptor() {
+ return uri.getPath();
+ }
+
+ @Override
+ public boolean hasSSLSupport() {
+ return socket instanceof SSLSocket;
+ }
+
+ @Override
+ public SSLSession getSSLSession() {
+ if (!hasSSLSupport()) {
+ throw new IllegalArgumentException(
+ "This websocket uses ws instead of wss. No SSLSession available.");
+ }
+ return ((SSLSocket)socket).getSession();
+ }
+
+ @Override
+ public IProtocol getProtocol() {
+ return engine.getProtocol();
+ }
+
+ /**
+ * Method to give some additional info for specific IOExceptions
+ *
+ * @param e the IOException causing a eot.
+ */
+ private void handleIOException(IOException e) {
+ if (e instanceof SSLException) {
+ onError(e);
+ }
+ engine.eot();
+ }
}
diff --git a/src/main/java/org/java_websocket/client/package-info.java b/src/main/java/org/java_websocket/client/package-info.java
index 376b699af..e6d799d5f 100644
--- a/src/main/java/org/java_websocket/client/package-info.java
+++ b/src/main/java/org/java_websocket/client/package-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
diff --git a/src/main/java/org/java_websocket/drafts/Draft.java b/src/main/java/org/java_websocket/drafts/Draft.java
index b6630608c..2cda1e5ca 100644
--- a/src/main/java/org/java_websocket/drafts/Draft.java
+++ b/src/main/java/org/java_websocket/drafts/Draft.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -30,15 +30,20 @@
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
-
-import org.java_websocket.WebSocket.Role;
import org.java_websocket.WebSocketImpl;
+import org.java_websocket.enums.CloseHandshakeType;
+import org.java_websocket.enums.HandshakeState;
+import org.java_websocket.enums.Opcode;
+import org.java_websocket.enums.Role;
import org.java_websocket.exceptions.IncompleteHandshakeException;
import org.java_websocket.exceptions.InvalidDataException;
import org.java_websocket.exceptions.InvalidHandshakeException;
-import org.java_websocket.exceptions.LimitExedeedException;
-import org.java_websocket.framing.*;
-import org.java_websocket.framing.Framedata.Opcode;
+import org.java_websocket.framing.BinaryFrame;
+import org.java_websocket.framing.CloseFrame;
+import org.java_websocket.framing.ContinuousFrame;
+import org.java_websocket.framing.DataFrame;
+import org.java_websocket.framing.Framedata;
+import org.java_websocket.framing.TextFrame;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.handshake.ClientHandshakeBuilder;
import org.java_websocket.handshake.HandshakeBuilder;
@@ -50,256 +55,301 @@
import org.java_websocket.util.Charsetfunctions;
/**
- * Base class for everything of a websocket specification which is not common such as the way the handshake is read or frames are transfered.
+ * Base class for everything of a websocket specification which is not common such as the way the
+ * handshake is read or frames are transferred.
**/
public abstract class Draft {
- /**
- * Enum which represents the states a handshake may be in
- */
- public enum HandshakeState {
- /** Handshake matched this Draft successfully */
- MATCHED,
- /** Handshake is does not match this Draft */
- NOT_MATCHED
- }
- /**
- * Enum which represents type of handshake is required for a close
- */
- public enum CloseHandshakeType {
- NONE, ONEWAY, TWOWAY
- }
-
- public static int MAX_FAME_SIZE = 1000;
- public static int INITIAL_FAMESIZE = 64;
-
- /** In some cases the handshake will be parsed different depending on whether */
- protected Role role = null;
-
- protected Opcode continuousFrameType = null;
-
- public static ByteBuffer readLine( ByteBuffer buf ) {
- ByteBuffer sbuf = ByteBuffer.allocate( buf.remaining() );
- byte prev;
- byte cur = '0';
- while ( buf.hasRemaining() ) {
- prev = cur;
- cur = buf.get();
- sbuf.put( cur );
- if( prev == (byte) '\r' && cur == (byte) '\n' ) {
- sbuf.limit( sbuf.position() - 2 );
- sbuf.position( 0 );
- return sbuf;
-
- }
- }
- // ensure that there wont be any bytes skipped
- buf.position( buf.position() - sbuf.position() );
- return null;
- }
-
- public static String readStringLine( ByteBuffer buf ) {
- ByteBuffer b = readLine( buf );
- return b == null ? null : Charsetfunctions.stringAscii( b.array(), 0, b.limit() );
- }
-
- public static HandshakeBuilder translateHandshakeHttp( ByteBuffer buf, Role role ) throws InvalidHandshakeException , IncompleteHandshakeException {
- HandshakeBuilder handshake;
-
- String line = readStringLine( buf );
- if( line == null )
- throw new IncompleteHandshakeException( buf.capacity() + 128 );
-
- String[] firstLineTokens = line.split( " ", 3 );// eg. HTTP/1.1 101 Switching the Protocols
- if( firstLineTokens.length != 3 ) {
- throw new InvalidHandshakeException();
- }
-
- if( role == Role.CLIENT ) {
- // translating/parsing the response from the SERVER
- if (!"101".equals(firstLineTokens[1])) {
- throw new InvalidHandshakeException( "Invalid status code received: " + firstLineTokens[1] + " Status line: " + line);
- }
- if (!"HTTP/1.1".equalsIgnoreCase(firstLineTokens[0])) {
- throw new InvalidHandshakeException( "Invalid status line received: " + firstLineTokens[0] + " Status line: " + line);
- }
-
- handshake = new HandshakeImpl1Server();
- ServerHandshakeBuilder serverhandshake = (ServerHandshakeBuilder) handshake;
- serverhandshake.setHttpStatus( Short.parseShort( firstLineTokens[ 1 ] ) );
- serverhandshake.setHttpStatusMessage( firstLineTokens[ 2 ] );
- } else {
- // translating/parsing the request from the CLIENT
- if (!"GET".equalsIgnoreCase(firstLineTokens[0])) {
- throw new InvalidHandshakeException( "Invalid request method received: " + firstLineTokens[0] + " Status line: " + line);
- }
- if (!"HTTP/1.1".equalsIgnoreCase(firstLineTokens[2])) {
- throw new InvalidHandshakeException( "Invalid status line received: " + firstLineTokens[2] + " Status line: " + line);
- }
- ClientHandshakeBuilder clienthandshake = new HandshakeImpl1Client();
- clienthandshake.setResourceDescriptor( firstLineTokens[ 1 ] );
- handshake = clienthandshake;
- }
-
- line = readStringLine( buf );
- while ( line != null && line.length() > 0 ) {
- String[] pair = line.split( ":", 2 );
- if( pair.length != 2 )
- throw new InvalidHandshakeException( "not an http header" );
- // If the handshake contains already a specific key, append the new value
- if ( handshake.hasFieldValue( pair[ 0 ] ) ) {
- handshake.put( pair[0], handshake.getFieldValue( pair[ 0 ] ) + "; " + pair[1].replaceFirst( "^ +", "" ) );
- } else {
- handshake.put( pair[0], pair[1].replaceFirst( "^ +", "" ) );
- }
- line = readStringLine( buf );
- }
- if( line == null )
- throw new IncompleteHandshakeException();
- return handshake;
- }
-
- public abstract HandshakeState acceptHandshakeAsClient( ClientHandshake request, ServerHandshake response ) throws InvalidHandshakeException;
-
- public abstract HandshakeState acceptHandshakeAsServer( ClientHandshake handshakedata ) throws InvalidHandshakeException;
-
- protected boolean basicAccept( Handshakedata handshakedata ) {
- return handshakedata.getFieldValue( "Upgrade" ).equalsIgnoreCase( "websocket" ) && handshakedata.getFieldValue( "Connection" ).toLowerCase( Locale.ENGLISH ).contains( "upgrade" );
- }
-
- public abstract ByteBuffer createBinaryFrame( Framedata framedata ); // TODO Allow to send data on the base of an Iterator or InputStream
-
- public abstract List createFrames( ByteBuffer binary, boolean mask );
-
- public abstract List createFrames( String text, boolean mask );
-
-
- /**
- * Handle the frame specific to the draft
- * @param webSocketImpl the websocketimpl used for this draft
- * @param frame the frame which is supposed to be handled
- * @throws InvalidDataException will be thrown on invalid data
- */
- public abstract void processFrame( WebSocketImpl webSocketImpl, Framedata frame ) throws InvalidDataException;
-
- public List continuousFrame( Opcode op, ByteBuffer buffer, boolean fin ) {
- if(op != Opcode.BINARY && op != Opcode.TEXT) {
- throw new IllegalArgumentException( "Only Opcode.BINARY or Opcode.TEXT are allowed" );
- }
- DataFrame bui = null;
- if( continuousFrameType != null ) {
- bui = new ContinuousFrame();
- } else {
- continuousFrameType = op;
- if (op == Opcode.BINARY) {
- bui = new BinaryFrame();
- } else if (op == Opcode.TEXT) {
- bui = new TextFrame();
- }
- }
- bui.setPayload( buffer );
- bui.setFin( fin );
- try {
- bui.isValid();
- } catch ( InvalidDataException e ) {
- throw new IllegalArgumentException( e ); // can only happen when one builds close frames(Opcode.Close)
- }
- if( fin ) {
- continuousFrameType = null;
- } else {
- continuousFrameType = op;
- }
- return Collections.singletonList( (Framedata) bui );
- }
-
- public abstract void reset();
-
- public List createHandshake( Handshakedata handshakedata, Role ownrole ) {
- return createHandshake( handshakedata, ownrole, true );
- }
-
- public List createHandshake( Handshakedata handshakedata, Role ownrole, boolean withcontent ) {
- StringBuilder bui = new StringBuilder( 100 );
- if( handshakedata instanceof ClientHandshake ) {
- bui.append( "GET " );
- bui.append( ( (ClientHandshake) handshakedata ).getResourceDescriptor() );
- bui.append( " HTTP/1.1" );
- } else if( handshakedata instanceof ServerHandshake ) {
- bui.append("HTTP/1.1 101 ").append(((ServerHandshake) handshakedata).getHttpStatusMessage());
- } else {
- throw new IllegalArgumentException( "unknown role" );
- }
- bui.append( "\r\n" );
- Iterator it = handshakedata.iterateHttpFields();
- while ( it.hasNext() ) {
- String fieldname = it.next();
- String fieldvalue = handshakedata.getFieldValue( fieldname );
- bui.append( fieldname );
- bui.append( ": " );
- bui.append( fieldvalue );
- bui.append( "\r\n" );
- }
- bui.append( "\r\n" );
- byte[] httpheader = Charsetfunctions.asciiBytes( bui.toString() );
-
- byte[] content = withcontent ? handshakedata.getContent() : null;
- ByteBuffer bytebuffer = ByteBuffer.allocate( ( content == null ? 0 : content.length ) + httpheader.length );
- bytebuffer.put( httpheader );
- if( content != null )
- bytebuffer.put( content );
- bytebuffer.flip();
- return Collections.singletonList( bytebuffer );
- }
-
- public abstract ClientHandshakeBuilder postProcessHandshakeRequestAsClient( ClientHandshakeBuilder request ) throws InvalidHandshakeException;
-
- public abstract HandshakeBuilder postProcessHandshakeResponseAsServer( ClientHandshake request, ServerHandshakeBuilder response ) throws InvalidHandshakeException;
-
- public abstract List translateFrame( ByteBuffer buffer ) throws InvalidDataException;
-
- public abstract CloseHandshakeType getCloseHandshakeType();
-
- /**
- * Drafts must only be by one websocket at all. To prevent drafts to be used more than once the Websocket implementation should call this method in order to create a new usable version of a given draft instance.
- * The copy can be safely used in conjunction with a new websocket connection.
- * @return a copy of the draft
- */
- public abstract Draft copyInstance();
-
- public Handshakedata translateHandshake( ByteBuffer buf ) throws InvalidHandshakeException {
- return translateHandshakeHttp( buf, role );
- }
-
- public int checkAlloc( int bytecount ) throws LimitExedeedException , InvalidDataException {
- if( bytecount < 0 )
- throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "Negative count" );
- return bytecount;
- }
-
- int readVersion( Handshakedata handshakedata ) {
- String vers = handshakedata.getFieldValue( "Sec-WebSocket-Version" );
- if( vers.length() > 0 ) {
- int v;
- try {
- v = new Integer( vers.trim() );
- return v;
- } catch ( NumberFormatException e ) {
- return -1;
- }
- }
- return -1;
- }
-
- public void setParseMode( Role role ) {
- this.role = role;
- }
-
- public Role getRole() {
- return role;
- }
-
- public String toString() {
- return getClass().getSimpleName();
- }
+ /**
+ * In some cases the handshake will be parsed different depending on whether
+ */
+ protected Role role = null;
+
+ protected Opcode continuousFrameType = null;
+
+ public static ByteBuffer readLine(ByteBuffer buf) {
+ ByteBuffer sbuf = ByteBuffer.allocate(buf.remaining());
+ byte prev;
+ byte cur = '0';
+ while (buf.hasRemaining()) {
+ prev = cur;
+ cur = buf.get();
+ sbuf.put(cur);
+ if (prev == (byte) '\r' && cur == (byte) '\n') {
+ sbuf.limit(sbuf.position() - 2);
+ sbuf.position(0);
+ return sbuf;
+
+ }
+ }
+ // ensure that there wont be any bytes skipped
+ buf.position(buf.position() - sbuf.position());
+ return null;
+ }
+
+ public static String readStringLine(ByteBuffer buf) {
+ ByteBuffer b = readLine(buf);
+ return b == null ? null : Charsetfunctions.stringAscii(b.array(), 0, b.limit());
+ }
+
+ public static HandshakeBuilder translateHandshakeHttp(ByteBuffer buf, Role role)
+ throws InvalidHandshakeException {
+ HandshakeBuilder handshake;
+
+ String line = readStringLine(buf);
+ if (line == null) {
+ throw new IncompleteHandshakeException(buf.capacity() + 128);
+ }
+
+ String[] firstLineTokens = line.split(" ", 3);// eg. HTTP/1.1 101 Switching the Protocols
+ if (firstLineTokens.length != 3) {
+ throw new InvalidHandshakeException();
+ }
+ if (role == Role.CLIENT) {
+ handshake = translateHandshakeHttpClient(firstLineTokens, line);
+ } else {
+ handshake = translateHandshakeHttpServer(firstLineTokens, line);
+ }
+ line = readStringLine(buf);
+ while (line != null && line.length() > 0) {
+ String[] pair = line.split(":", 2);
+ if (pair.length != 2) {
+ throw new InvalidHandshakeException("not an http header");
+ }
+ // If the handshake contains already a specific key, append the new value
+ if (handshake.hasFieldValue(pair[0])) {
+ handshake.put(pair[0],
+ handshake.getFieldValue(pair[0]) + "; " + pair[1].replaceFirst("^ +", ""));
+ } else {
+ handshake.put(pair[0], pair[1].replaceFirst("^ +", ""));
+ }
+ line = readStringLine(buf);
+ }
+ if (line == null) {
+ throw new IncompleteHandshakeException();
+ }
+ return handshake;
+ }
+
+ /**
+ * Checking the handshake for the role as server
+ *
+ * @param firstLineTokens the token of the first line split as as an string array
+ * @param line the whole line
+ * @return a handshake
+ */
+ private static HandshakeBuilder translateHandshakeHttpServer(String[] firstLineTokens,
+ String line) throws InvalidHandshakeException {
+ // translating/parsing the request from the CLIENT
+ if (!"GET".equalsIgnoreCase(firstLineTokens[0])) {
+ throw new InvalidHandshakeException(String
+ .format("Invalid request method received: %s Status line: %s", firstLineTokens[0], line));
+ }
+ if (!"HTTP/1.1".equalsIgnoreCase(firstLineTokens[2])) {
+ throw new InvalidHandshakeException(String
+ .format("Invalid status line received: %s Status line: %s", firstLineTokens[2], line));
+ }
+ ClientHandshakeBuilder clienthandshake = new HandshakeImpl1Client();
+ clienthandshake.setResourceDescriptor(firstLineTokens[1]);
+ return clienthandshake;
+ }
+
+ /**
+ * Checking the handshake for the role as client
+ *
+ * @param firstLineTokens the token of the first line split as as an string array
+ * @param line the whole line
+ * @return a handshake
+ */
+ private static HandshakeBuilder translateHandshakeHttpClient(String[] firstLineTokens,
+ String line) throws InvalidHandshakeException {
+ // translating/parsing the response from the SERVER
+ if (!"101".equals(firstLineTokens[1])) {
+ throw new InvalidHandshakeException(String
+ .format("Invalid status code received: %s Status line: %s", firstLineTokens[1], line));
+ }
+ if (!"HTTP/1.1".equalsIgnoreCase(firstLineTokens[0])) {
+ throw new InvalidHandshakeException(String
+ .format("Invalid status line received: %s Status line: %s", firstLineTokens[0], line));
+ }
+ HandshakeBuilder handshake = new HandshakeImpl1Server();
+ ServerHandshakeBuilder serverhandshake = (ServerHandshakeBuilder) handshake;
+ serverhandshake.setHttpStatus(Short.parseShort(firstLineTokens[1]));
+ serverhandshake.setHttpStatusMessage(firstLineTokens[2]);
+ return handshake;
+ }
+
+ public abstract HandshakeState acceptHandshakeAsClient(ClientHandshake request,
+ ServerHandshake response) throws InvalidHandshakeException;
+
+ public abstract HandshakeState acceptHandshakeAsServer(ClientHandshake handshakedata)
+ throws InvalidHandshakeException;
+
+ protected boolean basicAccept(Handshakedata handshakedata) {
+ return handshakedata.getFieldValue("Upgrade").equalsIgnoreCase("websocket") && handshakedata
+ .getFieldValue("Connection").toLowerCase(Locale.ENGLISH).contains("upgrade");
+ }
+
+ public abstract ByteBuffer createBinaryFrame(Framedata framedata);
+
+ public abstract List createFrames(ByteBuffer binary, boolean mask);
+
+ public abstract List createFrames(String text, boolean mask);
+
+
+ /**
+ * Handle the frame specific to the draft
+ *
+ * @param webSocketImpl the websocketimpl used for this draft
+ * @param frame the frame which is supposed to be handled
+ * @throws InvalidDataException will be thrown on invalid data
+ */
+ public abstract void processFrame(WebSocketImpl webSocketImpl, Framedata frame)
+ throws InvalidDataException;
+
+ public List continuousFrame(Opcode op, ByteBuffer buffer, boolean fin) {
+ if (op != Opcode.BINARY && op != Opcode.TEXT) {
+ throw new IllegalArgumentException("Only Opcode.BINARY or Opcode.TEXT are allowed");
+ }
+ DataFrame bui = null;
+ if (continuousFrameType != null) {
+ bui = new ContinuousFrame();
+ } else {
+ continuousFrameType = op;
+ if (op == Opcode.BINARY) {
+ bui = new BinaryFrame();
+ } else if (op == Opcode.TEXT) {
+ bui = new TextFrame();
+ }
+ }
+ bui.setPayload(buffer);
+ bui.setFin(fin);
+ try {
+ bui.isValid();
+ } catch (InvalidDataException e) {
+ throw new IllegalArgumentException(
+ e); // can only happen when one builds close frames(Opcode.Close)
+ }
+ if (fin) {
+ continuousFrameType = null;
+ } else {
+ continuousFrameType = op;
+ }
+ return Collections.singletonList((Framedata) bui);
+ }
+
+ public abstract void reset();
+
+ /**
+ * @deprecated use createHandshake without the role
+ */
+ @Deprecated
+ public List createHandshake(Handshakedata handshakedata, Role ownrole) {
+ return createHandshake(handshakedata);
+ }
+
+ public List createHandshake(Handshakedata handshakedata) {
+ return createHandshake(handshakedata, true);
+ }
+
+ /**
+ * @deprecated use createHandshake without the role since it does not have any effect
+ */
+ @Deprecated
+ public List createHandshake(Handshakedata handshakedata, Role ownrole,
+ boolean withcontent) {
+ return createHandshake(handshakedata, withcontent);
+ }
+
+ public List createHandshake(Handshakedata handshakedata, boolean withcontent) {
+ StringBuilder bui = new StringBuilder(100);
+ if (handshakedata instanceof ClientHandshake) {
+ bui.append("GET ").append(((ClientHandshake) handshakedata).getResourceDescriptor())
+ .append(" HTTP/1.1");
+ } else if (handshakedata instanceof ServerHandshake) {
+ bui.append("HTTP/1.1 101 ").append(((ServerHandshake) handshakedata).getHttpStatusMessage());
+ } else {
+ throw new IllegalArgumentException("unknown role");
+ }
+ bui.append("\r\n");
+ Iterator it = handshakedata.iterateHttpFields();
+ while (it.hasNext()) {
+ String fieldname = it.next();
+ String fieldvalue = handshakedata.getFieldValue(fieldname);
+ bui.append(fieldname);
+ bui.append(": ");
+ bui.append(fieldvalue);
+ bui.append("\r\n");
+ }
+ bui.append("\r\n");
+ byte[] httpheader = Charsetfunctions.asciiBytes(bui.toString());
+
+ byte[] content = withcontent ? handshakedata.getContent() : null;
+ ByteBuffer bytebuffer = ByteBuffer
+ .allocate((content == null ? 0 : content.length) + httpheader.length);
+ bytebuffer.put(httpheader);
+ if (content != null) {
+ bytebuffer.put(content);
+ }
+ bytebuffer.flip();
+ return Collections.singletonList(bytebuffer);
+ }
+
+ public abstract ClientHandshakeBuilder postProcessHandshakeRequestAsClient(
+ ClientHandshakeBuilder request) throws InvalidHandshakeException;
+
+ public abstract HandshakeBuilder postProcessHandshakeResponseAsServer(ClientHandshake request,
+ ServerHandshakeBuilder response) throws InvalidHandshakeException;
+
+ public abstract List translateFrame(ByteBuffer buffer) throws InvalidDataException;
+
+ public abstract CloseHandshakeType getCloseHandshakeType();
+
+ /**
+ * Drafts must only be by one websocket at all. To prevent drafts to be used more than once the
+ * Websocket implementation should call this method in order to create a new usable version of a
+ * given draft instance.
The copy can be safely used in conjunction with a new websocket
+ * connection.
+ *
+ * @return a copy of the draft
+ */
+ public abstract Draft copyInstance();
+
+ public Handshakedata translateHandshake(ByteBuffer buf) throws InvalidHandshakeException {
+ return translateHandshakeHttp(buf, role);
+ }
+
+ public int checkAlloc(int bytecount) throws InvalidDataException {
+ if (bytecount < 0) {
+ throw new InvalidDataException(CloseFrame.PROTOCOL_ERROR, "Negative count");
+ }
+ return bytecount;
+ }
+
+ int readVersion(Handshakedata handshakedata) {
+ String vers = handshakedata.getFieldValue("Sec-WebSocket-Version");
+ if (vers.length() > 0) {
+ int v;
+ try {
+ v = Integer.parseInt(vers.trim());
+ return v;
+ } catch (NumberFormatException e) {
+ return -1;
+ }
+ }
+ return -1;
+ }
+
+ public void setParseMode(Role role) {
+ this.role = role;
+ }
+
+ public Role getRole() {
+ return role;
+ }
+
+ public String toString() {
+ return getClass().getSimpleName();
+ }
}
diff --git a/src/main/java/org/java_websocket/drafts/Draft_6455.java b/src/main/java/org/java_websocket/drafts/Draft_6455.java
index 14853970b..eb4879976 100644
--- a/src/main/java/org/java_websocket/drafts/Draft_6455.java
+++ b/src/main/java/org/java_websocket/drafts/Draft_6455.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -25,740 +25,1189 @@
package org.java_websocket.drafts;
-import org.java_websocket.WebSocket;
-import org.java_websocket.WebSocketImpl;
-import org.java_websocket.exceptions.*;
-import org.java_websocket.extensions.*;
-import org.java_websocket.framing.*;
-import org.java_websocket.handshake.*;
-import org.java_websocket.protocols.IProtocol;
-import org.java_websocket.protocols.Protocol;
-import org.java_websocket.util.*;
-import org.java_websocket.util.Base64;
-
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
import java.text.SimpleDateFormat;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.TimeZone;
+import org.java_websocket.WebSocketImpl;
+import org.java_websocket.enums.CloseHandshakeType;
+import org.java_websocket.enums.HandshakeState;
+import org.java_websocket.enums.Opcode;
+import org.java_websocket.enums.ReadyState;
+import org.java_websocket.enums.Role;
+import org.java_websocket.exceptions.IncompleteException;
+import org.java_websocket.exceptions.InvalidDataException;
+import org.java_websocket.exceptions.InvalidFrameException;
+import org.java_websocket.exceptions.InvalidHandshakeException;
+import org.java_websocket.exceptions.LimitExceededException;
+import org.java_websocket.exceptions.NotSendableException;
+import org.java_websocket.extensions.DefaultExtension;
+import org.java_websocket.extensions.IExtension;
+import org.java_websocket.framing.BinaryFrame;
+import org.java_websocket.framing.CloseFrame;
+import org.java_websocket.framing.Framedata;
+import org.java_websocket.framing.FramedataImpl1;
+import org.java_websocket.framing.TextFrame;
+import org.java_websocket.handshake.ClientHandshake;
+import org.java_websocket.handshake.ClientHandshakeBuilder;
+import org.java_websocket.handshake.HandshakeBuilder;
+import org.java_websocket.handshake.ServerHandshake;
+import org.java_websocket.handshake.ServerHandshakeBuilder;
+import org.java_websocket.protocols.IProtocol;
+import org.java_websocket.protocols.Protocol;
+import org.java_websocket.util.Base64;
+import org.java_websocket.util.Charsetfunctions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
- * Implementation for the RFC 6455 websocket protocol
- * This is the recommended class for your websocket connection
+ * Implementation for the RFC 6455 websocket protocol This is the recommended class for your
+ * websocket connection
*/
public class Draft_6455 extends Draft {
- /**
- * Attribute for the used extension in this draft
- */
- private IExtension extension = new DefaultExtension();
-
- /**
- * Attribute for all available extension in this draft
- */
- private List knownExtensions;
-
- /**
- * Attribute for the used protocol in this draft
- */
- private IProtocol protocol;
-
- /**
- * Attribute for all available protocols in this draft
- */
- private List knownProtocols;
-
- /**
- * Attribute for the current continuous frame
- */
- private Framedata current_continuous_frame;
-
- /**
- * Attribute for the payload of the current continuous frame
- */
- private List byteBufferList;
-
- /**
- * Attribute for the current incomplete frame
- */
- private ByteBuffer incompleteframe;
-
- /**
- * Attribute for the reusable random instance
- */
- private final Random reuseableRandom = new Random();
-
- /**
- * Constructor for the websocket protocol specified by RFC 6455 with default extensions
- * @since 1.3.5
- */
- public Draft_6455() {
- this( Collections.emptyList() );
- }
-
- /**
- * Constructor for the websocket protocol specified by RFC 6455 with custom extensions
- *
- * @param inputExtension the extension which should be used for this draft
- * @since 1.3.5
- */
- public Draft_6455( IExtension inputExtension ) {
- this( Collections.singletonList( inputExtension ) );
- }
-
- /**
- * Constructor for the websocket protocol specified by RFC 6455 with custom extensions
- *
- * @param inputExtensions the extensions which should be used for this draft
- * @since 1.3.5
- */
- public Draft_6455( List inputExtensions ) {
- this( inputExtensions, Collections.singletonList( new Protocol( "" ) ));
- }
-
- /**
- * Constructor for the websocket protocol specified by RFC 6455 with custom extensions and protocols
- *
- * @param inputExtensions the extensions which should be used for this draft
- * @param inputProtocols the protocols which should be used for this draft
- *
- * @since 1.3.7
- */
- public Draft_6455( List inputExtensions , List inputProtocols ) {
- if (inputExtensions == null || inputProtocols == null) {
- throw new IllegalArgumentException();
- }
- knownExtensions = new ArrayList( inputExtensions.size());
- knownProtocols = new ArrayList( inputProtocols.size());
- boolean hasDefault = false;
- byteBufferList = new ArrayList();
- for( IExtension inputExtension : inputExtensions ) {
- if( inputExtension.getClass().equals( DefaultExtension.class ) ) {
- hasDefault = true;
- }
- }
- knownExtensions.addAll( inputExtensions );
- //We always add the DefaultExtension to implement the normal RFC 6455 specification
- if( !hasDefault ) {
- knownExtensions.add( this.knownExtensions.size(), extension );
- }
- knownProtocols.addAll( inputProtocols );
- }
-
- @Override
- public HandshakeState acceptHandshakeAsServer( ClientHandshake handshakedata ) throws InvalidHandshakeException {
- int v = readVersion( handshakedata );
- if( v != 13 )
- return HandshakeState.NOT_MATCHED;
- HandshakeState extensionState= HandshakeState.NOT_MATCHED;
- String requestedExtension = handshakedata.getFieldValue( "Sec-WebSocket-Extensions" );
- for( IExtension knownExtension : knownExtensions ) {
- if( knownExtension.acceptProvidedExtensionAsServer( requestedExtension ) ) {
- extension = knownExtension;
- extensionState = HandshakeState.MATCHED;
- break;
- }
- }
- HandshakeState protocolState = HandshakeState.NOT_MATCHED;
- String requestedProtocol = handshakedata.getFieldValue( "Sec-WebSocket-Protocol" );
- for( IProtocol knownProtocol : knownProtocols ) {
- if( knownProtocol.acceptProvidedProtocol( requestedProtocol ) ) {
- protocol = knownProtocol;
- protocolState = HandshakeState.MATCHED;
- break;
- }
- }
- if (protocolState == HandshakeState.MATCHED && extensionState == HandshakeState.MATCHED) {
- return HandshakeState.MATCHED;
- }
- return HandshakeState.NOT_MATCHED;
- }
-
- @Override
- public HandshakeState acceptHandshakeAsClient( ClientHandshake request, ServerHandshake response ) throws InvalidHandshakeException {
- if (! basicAccept( response )) {
- return HandshakeState.NOT_MATCHED;
- }
- if( !request.hasFieldValue( "Sec-WebSocket-Key" ) || !response.hasFieldValue( "Sec-WebSocket-Accept" ) )
- return HandshakeState.NOT_MATCHED;
-
- String seckey_answere = response.getFieldValue( "Sec-WebSocket-Accept" );
- String seckey_challenge = request.getFieldValue( "Sec-WebSocket-Key" );
- seckey_challenge = generateFinalKey( seckey_challenge );
-
- if( !seckey_challenge.equals( seckey_answere ) )
- return HandshakeState.NOT_MATCHED;
-
- HandshakeState extensionState= HandshakeState.NOT_MATCHED;
- String requestedExtension = response.getFieldValue( "Sec-WebSocket-Extensions" );
- for( IExtension knownExtension : knownExtensions ) {
- if( knownExtension.acceptProvidedExtensionAsClient( requestedExtension ) ) {
- extension = knownExtension;
- extensionState = HandshakeState.MATCHED;
- break;
- }
- }
- HandshakeState protocolState = HandshakeState.NOT_MATCHED;
- String requestedProtocol = response.getFieldValue( "Sec-WebSocket-Protocol" );
- for( IProtocol knownProtocol : knownProtocols ) {
- if( knownProtocol.acceptProvidedProtocol( requestedProtocol ) ) {
- protocol = knownProtocol;
- protocolState = HandshakeState.MATCHED;
- break;
- }
- }
- if (protocolState == HandshakeState.MATCHED && extensionState == HandshakeState.MATCHED) {
- return HandshakeState.MATCHED;
- }
- return HandshakeState.NOT_MATCHED;
- }
-
- /**
- * Getter for the extension which is used by this draft
- *
- * @return the extension which is used or null, if handshake is not yet done
- */
- public IExtension getExtension() {
- return extension;
- }
-
- /**
- * Getter for all available extensions for this draft
- * @return the extensions which are enabled for this draft
- */
- public List getKnownExtensions() {
- return knownExtensions;
- }
-
- /**
- * Getter for the protocol which is used by this draft
- *
- * @return the protocol which is used or null, if handshake is not yet done or no valid protocols
- * @since 1.3.7
- */
- public IProtocol getProtocol() {
- return protocol;
- }
-
- /**
- * Getter for all available protocols for this draft
- * @return the protocols which are enabled for this draft
- * @since 1.3.7
- */
- public List getKnownProtocols() {
- return knownProtocols;
- }
-
- @Override
- public ClientHandshakeBuilder postProcessHandshakeRequestAsClient( ClientHandshakeBuilder request ) {
- request.put( "Upgrade", "websocket" );
- request.put( "Connection", "Upgrade" ); // to respond to a Connection keep alives
- byte[] random = new byte[16];
- reuseableRandom.nextBytes( random );
- request.put( "Sec-WebSocket-Key", Base64.encodeBytes( random ) );
- request.put( "Sec-WebSocket-Version", "13" );// overwriting the previous
- StringBuilder requestedExtensions = new StringBuilder();
- for( IExtension knownExtension : knownExtensions ) {
- if( knownExtension.getProvidedExtensionAsClient() != null && knownExtension.getProvidedExtensionAsClient().length() != 0 ) {
- if (requestedExtensions.length() > 0) {
- requestedExtensions.append( ", " );
- }
- requestedExtensions.append( knownExtension.getProvidedExtensionAsClient() );
- }
- }
- if( requestedExtensions.length() != 0 ) {
- request.put( "Sec-WebSocket-Extensions", requestedExtensions.toString() );
- }
- StringBuilder requestedProtocols = new StringBuilder();
- for( IProtocol knownProtocol : knownProtocols ) {
- if( knownProtocol.getProvidedProtocol().length() != 0 ) {
- if (requestedProtocols.length() > 0) {
- requestedProtocols.append( ", " );
- }
- requestedProtocols.append( knownProtocol.getProvidedProtocol() );
- }
- }
- if( requestedProtocols.length() != 0 ) {
- request.put( "Sec-WebSocket-Protocol", requestedProtocols.toString() );
- }
- return request;
- }
-
- @Override
- public HandshakeBuilder postProcessHandshakeResponseAsServer( ClientHandshake request, ServerHandshakeBuilder response ) throws InvalidHandshakeException {
- response.put( "Upgrade", "websocket" );
- response.put( "Connection", request.getFieldValue( "Connection" ) ); // to respond to a Connection keep alives
- String seckey = request.getFieldValue( "Sec-WebSocket-Key" );
- if( seckey == null )
- throw new InvalidHandshakeException( "missing Sec-WebSocket-Key" );
- response.put( "Sec-WebSocket-Accept", generateFinalKey( seckey ) );
- if( getExtension().getProvidedExtensionAsServer().length() != 0 ) {
- response.put( "Sec-WebSocket-Extensions", getExtension().getProvidedExtensionAsServer() );
- }
- if( getProtocol() != null && getProtocol().getProvidedProtocol().length() != 0 ) {
- response.put( "Sec-WebSocket-Protocol", getProtocol().getProvidedProtocol() );
- }
- response.setHttpStatusMessage( "Web Socket Protocol Handshake" );
- response.put( "Server", "TooTallNate Java-WebSocket" );
- response.put( "Date", getServerTime() );
- return response;
- }
-
- @Override
- public Draft copyInstance() {
- ArrayList newExtensions = new ArrayList();
- for( IExtension extension : getKnownExtensions() ) {
- newExtensions.add( extension.copyInstance() );
- }
- ArrayList newProtocols = new ArrayList();
- for( IProtocol protocol : getKnownProtocols() ) {
- newProtocols.add( protocol.copyInstance() );
- }
- return new Draft_6455( newExtensions, newProtocols );
- }
-
- @Override
- public ByteBuffer createBinaryFrame( Framedata framedata ) {
- getExtension().encodeFrame( framedata );
- if( WebSocketImpl.DEBUG )
- System.out.println( "afterEnconding(" + framedata.getPayloadData().remaining() + "): {" + ( framedata.getPayloadData().remaining() > 1000 ? "too big to display" : new String( framedata.getPayloadData().array() ) ) + '}' );
- return createByteBufferFromFramedata( framedata );
- }
-
- private ByteBuffer createByteBufferFromFramedata( Framedata framedata ) {
- ByteBuffer mes = framedata.getPayloadData();
- boolean mask = role == WebSocket.Role.CLIENT; // framedata.getTransfereMasked();
- int sizebytes = mes.remaining() <= 125 ? 1 : mes.remaining() <= 65535 ? 2 : 8;
- ByteBuffer buf = ByteBuffer.allocate( 1 + ( sizebytes > 1 ? sizebytes + 1 : sizebytes ) + ( mask ? 4 : 0 ) + mes.remaining() );
- byte optcode = fromOpcode( framedata.getOpcode() );
- byte one = ( byte ) ( framedata.isFin() ? -128 : 0 );
- one |= optcode;
- buf.put( one );
- byte[] payloadlengthbytes = toByteArray( mes.remaining(), sizebytes );
- assert ( payloadlengthbytes.length == sizebytes );
-
- if( sizebytes == 1 ) {
- buf.put( ( byte ) ( payloadlengthbytes[0] | ( mask ? ( byte ) -128 : 0 ) ) );
- } else if( sizebytes == 2 ) {
- buf.put( ( byte ) ( ( byte ) 126 | ( mask ? ( byte ) -128 : 0 ) ) );
- buf.put( payloadlengthbytes );
- } else if( sizebytes == 8 ) {
- buf.put( ( byte ) ( ( byte ) 127 | ( mask ? ( byte ) -128 : 0 ) ) );
- buf.put( payloadlengthbytes );
- } else
- throw new RuntimeException( "Size representation not supported/specified" );
-
- if( mask ) {
- ByteBuffer maskkey = ByteBuffer.allocate( 4 );
- maskkey.putInt( reuseableRandom.nextInt() );
- buf.put( maskkey.array() );
- for( int i = 0; mes.hasRemaining(); i++ ) {
- buf.put( ( byte ) ( mes.get() ^ maskkey.get( i % 4 ) ) );
- }
- } else {
- buf.put( mes );
- //Reset the position of the bytebuffer e.g. for additional use
- mes.flip();
- }
- assert ( buf.remaining() == 0 ) : buf.remaining();
- buf.flip();
- return buf;
- }
-
- public Framedata translateSingleFrame( ByteBuffer buffer ) throws IncompleteException, InvalidDataException {
- int maxpacketsize = buffer.remaining();
- int realpacketsize = 2;
- if( maxpacketsize < realpacketsize )
- throw new IncompleteException( realpacketsize );
- byte b1 = buffer.get( /*0*/ );
- boolean FIN = b1 >> 8 != 0;
- boolean rsv1 = false;
- boolean rsv2 = false;
- boolean rsv3 = false;
- if( ( b1 & 0x40 ) != 0 ) {
- rsv1 = true;
- }
- if( ( b1 & 0x20 ) != 0 ) {
- rsv2 = true;
- }
- if( ( b1 & 0x10 ) != 0 ) {
- rsv3 = true;
- }
- byte b2 = buffer.get( /*1*/ );
- boolean MASK = ( b2 & -128 ) != 0;
- int payloadlength = ( byte ) ( b2 & ~( byte ) 128 );
- Framedata.Opcode optcode = toOpcode( ( byte ) ( b1 & 15 ) );
-
- if( !( payloadlength >= 0 && payloadlength <= 125 ) ) {
- if( optcode == Framedata.Opcode.PING || optcode == Framedata.Opcode.PONG || optcode == Framedata.Opcode.CLOSING ) {
- throw new InvalidFrameException( "more than 125 octets" );
- }
- if( payloadlength == 126 ) {
- realpacketsize += 2; // additional length bytes
- if( maxpacketsize < realpacketsize )
- throw new IncompleteException( realpacketsize );
- byte[] sizebytes = new byte[3];
- sizebytes[1] = buffer.get( /*1 + 1*/ );
- sizebytes[2] = buffer.get( /*1 + 2*/ );
- payloadlength = new BigInteger( sizebytes ).intValue();
- } else {
- realpacketsize += 8; // additional length bytes
- if( maxpacketsize < realpacketsize )
- throw new IncompleteException( realpacketsize );
- byte[] bytes = new byte[8];
- for( int i = 0; i < 8; i++ ) {
- bytes[i] = buffer.get( /*1 + i*/ );
- }
- long length = new BigInteger( bytes ).longValue();
- if( length > Integer.MAX_VALUE ) {
- throw new LimitExedeedException( "Payloadsize is to big..." );
- } else {
- payloadlength = ( int ) length;
- }
- }
- }
-
- // int maskskeystart = foff + realpacketsize;
- realpacketsize += ( MASK ? 4 : 0 );
- // int payloadstart = foff + realpacketsize;
- realpacketsize += payloadlength;
-
- if( maxpacketsize < realpacketsize )
- throw new IncompleteException( realpacketsize );
-
- ByteBuffer payload = ByteBuffer.allocate( checkAlloc( payloadlength ) );
- if( MASK ) {
- byte[] maskskey = new byte[4];
- buffer.get( maskskey );
- for( int i = 0; i < payloadlength; i++ ) {
- payload.put( ( byte ) ( buffer.get( /*payloadstart + i*/ ) ^ maskskey[i % 4] ) );
- }
- } else {
- payload.put( buffer.array(), buffer.position(), payload.limit() );
- buffer.position( buffer.position() + payload.limit() );
- }
-
- FramedataImpl1 frame = FramedataImpl1.get( optcode );
- frame.setFin( FIN );
- frame.setRSV1( rsv1 );
- frame.setRSV2( rsv2 );
- frame.setRSV3( rsv3 );
- payload.flip();
- frame.setPayload( payload );
- getExtension().isFrameValid(frame);
- getExtension().decodeFrame(frame);
- if( WebSocketImpl.DEBUG )
- System.out.println( "afterDecoding(" + frame.getPayloadData().remaining() + "): {" + ( frame.getPayloadData().remaining() > 1000 ? "too big to display" : new String( frame.getPayloadData().array() ) ) + '}' );
- frame.isValid();
- return frame;
- }
-
-
- @Override
- public List translateFrame( ByteBuffer buffer ) throws InvalidDataException {
- while( true ) {
- List frames = new LinkedList();
- Framedata cur;
- if( incompleteframe != null ) {
- // complete an incomplete frame
- try {
- buffer.mark();
- int available_next_byte_count = buffer.remaining();// The number of bytes received
- int expected_next_byte_count = incompleteframe.remaining();// The number of bytes to complete the incomplete frame
-
- if( expected_next_byte_count > available_next_byte_count ) {
- // did not receive enough bytes to complete the frame
- incompleteframe.put( buffer.array(), buffer.position(), available_next_byte_count );
- buffer.position( buffer.position() + available_next_byte_count );
- return Collections.emptyList();
- }
- incompleteframe.put( buffer.array(), buffer.position(), expected_next_byte_count );
- buffer.position( buffer.position() + expected_next_byte_count );
- cur = translateSingleFrame( ( ByteBuffer ) incompleteframe.duplicate().position( 0 ) );
- frames.add( cur );
- incompleteframe = null;
- } catch ( IncompleteException e ) {
- // extending as much as suggested
- ByteBuffer extendedframe = ByteBuffer.allocate( checkAlloc( e.getPreferredSize() ) );
- assert ( extendedframe.limit() > incompleteframe.limit() );
- incompleteframe.rewind();
- extendedframe.put( incompleteframe );
- incompleteframe = extendedframe;
- continue;
- }
- }
-
- while( buffer.hasRemaining() ) {// Read as much as possible full frames
- buffer.mark();
- try {
- cur = translateSingleFrame( buffer );
- frames.add( cur );
- } catch ( IncompleteException e ) {
- // remember the incomplete data
- buffer.reset();
- int pref = e.getPreferredSize();
- incompleteframe = ByteBuffer.allocate( checkAlloc( pref ) );
- incompleteframe.put( buffer );
- break;
- }
- }
- return frames;
- }
- }
-
- @Override
- public List createFrames( ByteBuffer binary, boolean mask ) {
- BinaryFrame curframe = new BinaryFrame();
- curframe.setPayload( binary );
- curframe.setTransferemasked( mask );
- try {
- curframe.isValid();
- } catch ( InvalidDataException e ) {
- throw new NotSendableException( e );
- }
- return Collections.singletonList( ( Framedata ) curframe );
- }
-
- @Override
- public List createFrames( String text, boolean mask ) {
- TextFrame curframe = new TextFrame();
- curframe.setPayload( ByteBuffer.wrap( Charsetfunctions.utf8Bytes( text ) ) );
- curframe.setTransferemasked( mask );
- try {
- curframe.isValid();
- } catch ( InvalidDataException e ) {
- throw new NotSendableException( e );
- }
- return Collections.singletonList( ( Framedata ) curframe );
- }
-
- @Override
- public void reset() {
- incompleteframe = null;
- if( extension != null ) {
- extension.reset();
- }
- extension = new DefaultExtension();
- protocol = null;
- }
-
- /**
- * Generate a date for for the date-header
- *
- * @return the server time
- */
- private String getServerTime() {
- Calendar calendar = Calendar.getInstance();
- SimpleDateFormat dateFormat = new SimpleDateFormat(
- "EEE, dd MMM yyyy HH:mm:ss z", Locale.US );
- dateFormat.setTimeZone( TimeZone.getTimeZone( "GMT" ) );
- return dateFormat.format( calendar.getTime() );
- }
-
- /**
- * Generate a final key from a input string
- * @param in the input string
- * @return a final key
- */
- private String generateFinalKey( String in ) {
- String seckey = in.trim();
- String acc = seckey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
- MessageDigest sh1;
- try {
- sh1 = MessageDigest.getInstance( "SHA1" );
- } catch ( NoSuchAlgorithmException e ) {
- throw new IllegalStateException( e );
- }
- return Base64.encodeBytes( sh1.digest( acc.getBytes() ) );
- }
-
- private byte[] toByteArray( long val, int bytecount ) {
- byte[] buffer = new byte[bytecount];
- int highest = 8 * bytecount - 8;
- for( int i = 0; i < bytecount; i++ ) {
- buffer[i] = ( byte ) ( val >>> ( highest - 8 * i ) );
- }
- return buffer;
- }
-
-
- private byte fromOpcode( Framedata.Opcode opcode ) {
- if( opcode == Framedata.Opcode.CONTINUOUS )
- return 0;
- else if( opcode == Framedata.Opcode.TEXT )
- return 1;
- else if( opcode == Framedata.Opcode.BINARY )
- return 2;
- else if( opcode == Framedata.Opcode.CLOSING )
- return 8;
- else if( opcode == Framedata.Opcode.PING )
- return 9;
- else if( opcode == Framedata.Opcode.PONG )
- return 10;
- throw new IllegalArgumentException( "Don't know how to handle " + opcode.toString() );
- }
-
-
- private Framedata.Opcode toOpcode( byte opcode ) throws InvalidFrameException {
- switch(opcode) {
- case 0:
- return Framedata.Opcode.CONTINUOUS;
- case 1:
- return Framedata.Opcode.TEXT;
- case 2:
- return Framedata.Opcode.BINARY;
- // 3-7 are not yet defined
- case 8:
- return Framedata.Opcode.CLOSING;
- case 9:
- return Framedata.Opcode.PING;
- case 10:
- return Framedata.Opcode.PONG;
- // 11-15 are not yet defined
- default:
- throw new InvalidFrameException( "Unknown opcode " + ( short ) opcode );
- }
- }
-
- @Override
- public void processFrame( WebSocketImpl webSocketImpl, Framedata frame ) throws InvalidDataException {
- Framedata.Opcode curop = frame.getOpcode();
- if( curop == Framedata.Opcode.CLOSING ) {
- int code = CloseFrame.NOCODE;
- String reason = "";
- if( frame instanceof CloseFrame ) {
- CloseFrame cf = ( CloseFrame ) frame;
- code = cf.getCloseCode();
- reason = cf.getMessage();
- }
- if( webSocketImpl.getReadyState() == WebSocket.READYSTATE.CLOSING ) {
- // complete the close handshake by disconnecting
- webSocketImpl.closeConnection( code, reason, true );
- } else {
- // echo close handshake
- if( getCloseHandshakeType() == CloseHandshakeType.TWOWAY )
- webSocketImpl.close( code, reason, true );
- else
- webSocketImpl.flushAndClose( code, reason, false );
- }
- } else if( curop == Framedata.Opcode.PING ) {
- webSocketImpl.getWebSocketListener().onWebsocketPing( webSocketImpl, frame );
- } else if( curop == Framedata.Opcode.PONG ) {
- webSocketImpl.updateLastPong();
- webSocketImpl.getWebSocketListener().onWebsocketPong( webSocketImpl, frame );
- } else if( !frame.isFin() || curop == Framedata.Opcode.CONTINUOUS ) {
- if( curop != Framedata.Opcode.CONTINUOUS ) {
- if( current_continuous_frame != null )
- throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "Previous continuous frame sequence not completed." );
- current_continuous_frame = frame;
- byteBufferList.add( frame.getPayloadData() );
- } else if( frame.isFin() ) {
- if( current_continuous_frame == null )
- throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "Continuous frame sequence was not started." );
- byteBufferList.add( frame.getPayloadData() );
- if( current_continuous_frame.getOpcode() == Framedata.Opcode.TEXT ) {
- ((FramedataImpl1) current_continuous_frame).setPayload( getPayloadFromByteBufferList() );
- ((FramedataImpl1) current_continuous_frame ).isValid();
- try {
- webSocketImpl.getWebSocketListener().onWebsocketMessage( webSocketImpl, Charsetfunctions.stringUtf8( current_continuous_frame.getPayloadData() ) );
- } catch ( RuntimeException e ) {
- webSocketImpl.getWebSocketListener().onWebsocketError( webSocketImpl, e );
- }
- } else if( current_continuous_frame.getOpcode() == Framedata.Opcode.BINARY ) {
- ((FramedataImpl1) current_continuous_frame).setPayload( getPayloadFromByteBufferList() );
- ((FramedataImpl1) current_continuous_frame ).isValid();
- try {
- webSocketImpl.getWebSocketListener().onWebsocketMessage( webSocketImpl, current_continuous_frame.getPayloadData() );
- } catch ( RuntimeException e ) {
- webSocketImpl.getWebSocketListener().onWebsocketError( webSocketImpl, e );
- }
- }
- current_continuous_frame = null;
- byteBufferList.clear();
- } else if( current_continuous_frame == null ) {
- throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "Continuous frame sequence was not started." );
- }
- //Check if the whole payload is valid utf8, when the opcode indicates a text
- if( curop == Framedata.Opcode.TEXT ) {
- if( !Charsetfunctions.isValidUTF8( frame.getPayloadData() ) ) {
- throw new InvalidDataException( CloseFrame.NO_UTF8 );
- }
- }
- //Checking if the current continous frame contains a correct payload with the other frames combined
- if( curop == Framedata.Opcode.CONTINUOUS && current_continuous_frame != null ) {
- byteBufferList.add( frame.getPayloadData() );
- }
- } else if( current_continuous_frame != null ) {
- throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "Continuous frame sequence not completed." );
- } else if( curop == Framedata.Opcode.TEXT ) {
- try {
- webSocketImpl.getWebSocketListener().onWebsocketMessage( webSocketImpl, Charsetfunctions.stringUtf8( frame.getPayloadData() ) );
- } catch ( RuntimeException e ) {
- webSocketImpl.getWebSocketListener().onWebsocketError( webSocketImpl, e );
- }
- } else if( curop == Framedata.Opcode.BINARY ) {
- try {
- webSocketImpl.getWebSocketListener().onWebsocketMessage( webSocketImpl, frame.getPayloadData() );
- } catch ( RuntimeException e ) {
- webSocketImpl.getWebSocketListener().onWebsocketError( webSocketImpl, e );
- }
- } else {
- throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "non control or continious frame expected" );
- }
- }
-
- @Override
- public Draft.CloseHandshakeType getCloseHandshakeType() {
- return Draft.CloseHandshakeType.TWOWAY;
- }
-
- @Override
- public String toString() {
- String result = super.toString();
- if( getExtension() != null )
- result += " extension: " + getExtension().toString();
- if ( getProtocol() != null )
- result += " protocol: " + getProtocol().toString();
- return result;
- }
-
- @Override
- public boolean equals( Object o ) {
- if( this == o ) return true;
- if( o == null || getClass() != o.getClass() ) return false;
-
- Draft_6455 that = ( Draft_6455 ) o;
-
- if( extension != null ? !extension.equals( that.extension ) : that.extension != null ) return false;
- return protocol != null ? protocol.equals( that.protocol ) : that.protocol == null;
- }
-
- @Override
- public int hashCode() {
- int result = extension != null ? extension.hashCode() : 0;
- result = 31 * result + ( protocol != null ? protocol.hashCode() : 0 );
- return result;
- }
-
- /**
- * Method to generate a full bytebuffer out of all the fragmented frame payload
- * @return a bytebuffer containing all the data
- * @throws LimitExedeedException will be thrown when the totalSize is bigger then Integer.MAX_VALUE due to not being able to allocate more
- */
- private ByteBuffer getPayloadFromByteBufferList() throws LimitExedeedException {
- long totalSize = 0;
- for (ByteBuffer buffer : byteBufferList) {
- totalSize +=buffer.limit();
- }
- if (totalSize > Integer.MAX_VALUE) {
- throw new LimitExedeedException( "Payloadsize is to big..." );
- }
- ByteBuffer resultingByteBuffer = ByteBuffer.allocate( (int) totalSize );
- for (ByteBuffer buffer : byteBufferList) {
- resultingByteBuffer.put( buffer );
- }
- resultingByteBuffer.flip();
- return resultingByteBuffer;
- }
+ /**
+ * Handshake specific field for the key
+ */
+ private static final String SEC_WEB_SOCKET_KEY = "Sec-WebSocket-Key";
+
+ /**
+ * Handshake specific field for the protocol
+ */
+ private static final String SEC_WEB_SOCKET_PROTOCOL = "Sec-WebSocket-Protocol";
+
+ /**
+ * Handshake specific field for the extension
+ */
+ private static final String SEC_WEB_SOCKET_EXTENSIONS = "Sec-WebSocket-Extensions";
+
+ /**
+ * Handshake specific field for the accept
+ */
+ private static final String SEC_WEB_SOCKET_ACCEPT = "Sec-WebSocket-Accept";
+
+ /**
+ * Handshake specific field for the upgrade
+ */
+ private static final String UPGRADE = "Upgrade";
+
+ /**
+ * Handshake specific field for the connection
+ */
+ private static final String CONNECTION = "Connection";
+
+ /**
+ * Logger instance
+ *
+ * @since 1.4.0
+ */
+ private final Logger log = LoggerFactory.getLogger(Draft_6455.class);
+
+ /**
+ * Attribute for the used extension in this draft
+ */
+ private IExtension negotiatedExtension = new DefaultExtension();
+
+ /**
+ * Attribute for the default extension
+ */
+ private IExtension defaultExtension = new DefaultExtension();
+
+ /**
+ * Attribute for all available extension in this draft
+ */
+ private List knownExtensions;
+
+ /**
+ * Current active extension used to decode messages
+ */
+ private IExtension currentDecodingExtension;
+
+ /**
+ * Attribute for the used protocol in this draft
+ */
+ private IProtocol protocol;
+
+ /**
+ * Attribute for all available protocols in this draft
+ */
+ private List knownProtocols;
+
+ /**
+ * Attribute for the current continuous frame
+ */
+ private Framedata currentContinuousFrame;
+
+ /**
+ * Attribute for the payload of the current continuous frame
+ */
+ private final List byteBufferList;
+
+ /**
+ * Attribute for the current incomplete frame
+ */
+ private ByteBuffer incompleteframe;
+
+ /**
+ * Attribute for the reusable random instance
+ */
+ private final SecureRandom reuseableRandom = new SecureRandom();
+
+ /**
+ * Attribute for the maximum allowed size of a frame
+ *
+ * @since 1.4.0
+ */
+ private int maxFrameSize;
+
+ /**
+ * Constructor for the websocket protocol specified by RFC 6455 with default extensions
+ *
+ * @since 1.3.5
+ */
+ public Draft_6455() {
+ this(Collections.emptyList());
+ }
+
+ /**
+ * Constructor for the websocket protocol specified by RFC 6455 with custom extensions
+ *
+ * @param inputExtension the extension which should be used for this draft
+ * @since 1.3.5
+ */
+ public Draft_6455(IExtension inputExtension) {
+ this(Collections.singletonList(inputExtension));
+ }
+
+ /**
+ * Constructor for the websocket protocol specified by RFC 6455 with custom extensions
+ *
+ * @param inputExtensions the extensions which should be used for this draft
+ * @since 1.3.5
+ */
+ public Draft_6455(List inputExtensions) {
+ this(inputExtensions, Collections.singletonList(new Protocol("")));
+ }
+
+ /**
+ * Constructor for the websocket protocol specified by RFC 6455 with custom extensions and
+ * protocols
+ *
+ * @param inputExtensions the extensions which should be used for this draft
+ * @param inputProtocols the protocols which should be used for this draft
+ * @since 1.3.7
+ */
+ public Draft_6455(List inputExtensions, List inputProtocols) {
+ this(inputExtensions, inputProtocols, Integer.MAX_VALUE);
+ }
+
+ /**
+ * Constructor for the websocket protocol specified by RFC 6455 with custom extensions and
+ * protocols
+ *
+ * @param inputExtensions the extensions which should be used for this draft
+ * @param inputMaxFrameSize the maximum allowed size of a frame (the real payload size, decoded
+ * frames can be bigger)
+ * @since 1.4.0
+ */
+ public Draft_6455(List inputExtensions, int inputMaxFrameSize) {
+ this(inputExtensions, Collections.singletonList(new Protocol("")),
+ inputMaxFrameSize);
+ }
+
+ /**
+ * Constructor for the websocket protocol specified by RFC 6455 with custom extensions and
+ * protocols
+ *
+ * @param inputExtensions the extensions which should be used for this draft
+ * @param inputProtocols the protocols which should be used for this draft
+ * @param inputMaxFrameSize the maximum allowed size of a frame (the real payload size, decoded
+ * frames can be bigger)
+ * @since 1.4.0
+ */
+ public Draft_6455(List inputExtensions, List inputProtocols,
+ int inputMaxFrameSize) {
+ if (inputExtensions == null || inputProtocols == null || inputMaxFrameSize < 1) {
+ throw new IllegalArgumentException();
+ }
+ knownExtensions = new ArrayList<>(inputExtensions.size());
+ knownProtocols = new ArrayList<>(inputProtocols.size());
+ boolean hasDefault = false;
+ byteBufferList = new ArrayList<>();
+ for (IExtension inputExtension : inputExtensions) {
+ if (inputExtension.getClass().equals(DefaultExtension.class)) {
+ hasDefault = true;
+ }
+ }
+ knownExtensions.addAll(inputExtensions);
+ //We always add the DefaultExtension to implement the normal RFC 6455 specification
+ if (!hasDefault) {
+ knownExtensions.add(this.knownExtensions.size(), negotiatedExtension);
+ }
+ knownProtocols.addAll(inputProtocols);
+ maxFrameSize = inputMaxFrameSize;
+ currentDecodingExtension = null;
+ }
+
+ @Override
+ public HandshakeState acceptHandshakeAsServer(ClientHandshake handshakedata)
+ throws InvalidHandshakeException {
+ int v = readVersion(handshakedata);
+ if (v != 13) {
+ log.trace("acceptHandshakeAsServer - Wrong websocket version.");
+ return HandshakeState.NOT_MATCHED;
+ }
+ HandshakeState extensionState = HandshakeState.NOT_MATCHED;
+ String requestedExtension = handshakedata.getFieldValue(SEC_WEB_SOCKET_EXTENSIONS);
+ for (IExtension knownExtension : knownExtensions) {
+ if (knownExtension.acceptProvidedExtensionAsServer(requestedExtension)) {
+ negotiatedExtension = knownExtension;
+ extensionState = HandshakeState.MATCHED;
+ log.trace("acceptHandshakeAsServer - Matching extension found: {}", negotiatedExtension);
+ break;
+ }
+ }
+ HandshakeState protocolState = containsRequestedProtocol(
+ handshakedata.getFieldValue(SEC_WEB_SOCKET_PROTOCOL));
+ if (protocolState == HandshakeState.MATCHED && extensionState == HandshakeState.MATCHED) {
+ return HandshakeState.MATCHED;
+ }
+ log.trace("acceptHandshakeAsServer - No matching extension or protocol found.");
+ return HandshakeState.NOT_MATCHED;
+ }
+
+ /**
+ * Check if the requested protocol is part of this draft
+ *
+ * @param requestedProtocol the requested protocol
+ * @return MATCHED if it is matched, otherwise NOT_MATCHED
+ */
+ private HandshakeState containsRequestedProtocol(String requestedProtocol) {
+ for (IProtocol knownProtocol : knownProtocols) {
+ if (knownProtocol.acceptProvidedProtocol(requestedProtocol)) {
+ protocol = knownProtocol;
+ log.trace("acceptHandshake - Matching protocol found: {}", protocol);
+ return HandshakeState.MATCHED;
+ }
+ }
+ return HandshakeState.NOT_MATCHED;
+ }
+
+ @Override
+ public HandshakeState acceptHandshakeAsClient(ClientHandshake request, ServerHandshake response)
+ throws InvalidHandshakeException {
+ if (!basicAccept(response)) {
+ log.trace("acceptHandshakeAsClient - Missing/wrong upgrade or connection in handshake.");
+ return HandshakeState.NOT_MATCHED;
+ }
+ if (!request.hasFieldValue(SEC_WEB_SOCKET_KEY) || !response
+ .hasFieldValue(SEC_WEB_SOCKET_ACCEPT)) {
+ log.trace("acceptHandshakeAsClient - Missing Sec-WebSocket-Key or Sec-WebSocket-Accept");
+ return HandshakeState.NOT_MATCHED;
+ }
+
+ String seckeyAnswer = response.getFieldValue(SEC_WEB_SOCKET_ACCEPT);
+ String seckeyChallenge = request.getFieldValue(SEC_WEB_SOCKET_KEY);
+ seckeyChallenge = generateFinalKey(seckeyChallenge);
+
+ if (!seckeyChallenge.equals(seckeyAnswer)) {
+ log.trace("acceptHandshakeAsClient - Wrong key for Sec-WebSocket-Key.");
+ return HandshakeState.NOT_MATCHED;
+ }
+ HandshakeState extensionState = HandshakeState.NOT_MATCHED;
+ String requestedExtension = response.getFieldValue(SEC_WEB_SOCKET_EXTENSIONS);
+ for (IExtension knownExtension : knownExtensions) {
+ if (knownExtension.acceptProvidedExtensionAsClient(requestedExtension)) {
+ negotiatedExtension = knownExtension;
+ extensionState = HandshakeState.MATCHED;
+ log.trace("acceptHandshakeAsClient - Matching extension found: {}", negotiatedExtension);
+ break;
+ }
+ }
+ HandshakeState protocolState = containsRequestedProtocol(
+ response.getFieldValue(SEC_WEB_SOCKET_PROTOCOL));
+ if (protocolState == HandshakeState.MATCHED && extensionState == HandshakeState.MATCHED) {
+ return HandshakeState.MATCHED;
+ }
+ log.trace("acceptHandshakeAsClient - No matching extension or protocol found.");
+ return HandshakeState.NOT_MATCHED;
+ }
+
+ /**
+ * Getter for the extension which is used by this draft
+ *
+ * @return the extension which is used or null, if handshake is not yet done
+ */
+ public IExtension getExtension() {
+ return negotiatedExtension;
+ }
+
+ /**
+ * Getter for all available extensions for this draft
+ *
+ * @return the extensions which are enabled for this draft
+ */
+ public List getKnownExtensions() {
+ return knownExtensions;
+ }
+
+ /**
+ * Getter for the protocol which is used by this draft
+ *
+ * @return the protocol which is used or null, if handshake is not yet done or no valid protocols
+ * @since 1.3.7
+ */
+ public IProtocol getProtocol() {
+ return protocol;
+ }
+
+
+ /**
+ * Getter for the maximum allowed payload size which is used by this draft
+ *
+ * @return the size, which is allowed for the payload
+ * @since 1.4.0
+ */
+ public int getMaxFrameSize() {
+ return maxFrameSize;
+ }
+
+ /**
+ * Getter for all available protocols for this draft
+ *
+ * @return the protocols which are enabled for this draft
+ * @since 1.3.7
+ */
+ public List getKnownProtocols() {
+ return knownProtocols;
+ }
+
+ @Override
+ public ClientHandshakeBuilder postProcessHandshakeRequestAsClient(
+ ClientHandshakeBuilder request) {
+ request.put(UPGRADE, "websocket");
+ request.put(CONNECTION, UPGRADE); // to respond to a Connection keep alives
+ byte[] random = new byte[16];
+ reuseableRandom.nextBytes(random);
+ request.put(SEC_WEB_SOCKET_KEY, Base64.encodeBytes(random));
+ request.put("Sec-WebSocket-Version", "13");// overwriting the previous
+ StringBuilder requestedExtensions = new StringBuilder();
+ for (IExtension knownExtension : knownExtensions) {
+ if (knownExtension.getProvidedExtensionAsClient() != null
+ && knownExtension.getProvidedExtensionAsClient().length() != 0) {
+ if (requestedExtensions.length() > 0) {
+ requestedExtensions.append(", ");
+ }
+ requestedExtensions.append(knownExtension.getProvidedExtensionAsClient());
+ }
+ }
+ if (requestedExtensions.length() != 0) {
+ request.put(SEC_WEB_SOCKET_EXTENSIONS, requestedExtensions.toString());
+ }
+ StringBuilder requestedProtocols = new StringBuilder();
+ for (IProtocol knownProtocol : knownProtocols) {
+ if (knownProtocol.getProvidedProtocol().length() != 0) {
+ if (requestedProtocols.length() > 0) {
+ requestedProtocols.append(", ");
+ }
+ requestedProtocols.append(knownProtocol.getProvidedProtocol());
+ }
+ }
+ if (requestedProtocols.length() != 0) {
+ request.put(SEC_WEB_SOCKET_PROTOCOL, requestedProtocols.toString());
+ }
+ return request;
+ }
+
+ @Override
+ public HandshakeBuilder postProcessHandshakeResponseAsServer(ClientHandshake request,
+ ServerHandshakeBuilder response) throws InvalidHandshakeException {
+ response.put(UPGRADE, "websocket");
+ response.put(CONNECTION,
+ request.getFieldValue(CONNECTION)); // to respond to a Connection keep alives
+ String seckey = request.getFieldValue(SEC_WEB_SOCKET_KEY);
+ if (seckey == null || "".equals(seckey)) {
+ throw new InvalidHandshakeException("missing Sec-WebSocket-Key");
+ }
+ response.put(SEC_WEB_SOCKET_ACCEPT, generateFinalKey(seckey));
+ if (getExtension().getProvidedExtensionAsServer().length() != 0) {
+ response.put(SEC_WEB_SOCKET_EXTENSIONS, getExtension().getProvidedExtensionAsServer());
+ }
+ if (getProtocol() != null && getProtocol().getProvidedProtocol().length() != 0) {
+ response.put(SEC_WEB_SOCKET_PROTOCOL, getProtocol().getProvidedProtocol());
+ }
+ response.setHttpStatusMessage("Web Socket Protocol Handshake");
+ response.put("Server", "TooTallNate Java-WebSocket");
+ response.put("Date", getServerTime());
+ return response;
+ }
+
+ @Override
+ public Draft copyInstance() {
+ ArrayList newExtensions = new ArrayList<>();
+ for (IExtension knownExtension : getKnownExtensions()) {
+ newExtensions.add(knownExtension.copyInstance());
+ }
+ ArrayList newProtocols = new ArrayList<>();
+ for (IProtocol knownProtocol : getKnownProtocols()) {
+ newProtocols.add(knownProtocol.copyInstance());
+ }
+ return new Draft_6455(newExtensions, newProtocols, maxFrameSize);
+ }
+
+ @Override
+ public ByteBuffer createBinaryFrame(Framedata framedata) {
+ getExtension().encodeFrame(framedata);
+ if (log.isTraceEnabled()) {
+ log.trace("afterEnconding({}): {}", framedata.getPayloadData().remaining(),
+ (framedata.getPayloadData().remaining() > 1000 ? "too big to display"
+ : new String(framedata.getPayloadData().array())));
+ }
+ return createByteBufferFromFramedata(framedata);
+ }
+
+ private ByteBuffer createByteBufferFromFramedata(Framedata framedata) {
+ ByteBuffer mes = framedata.getPayloadData();
+ boolean mask = role == Role.CLIENT;
+ int sizebytes = getSizeBytes(mes);
+ ByteBuffer buf = ByteBuffer.allocate(
+ 1 + (sizebytes > 1 ? sizebytes + 1 : sizebytes) + (mask ? 4 : 0) + mes.remaining());
+ byte optcode = fromOpcode(framedata.getOpcode());
+ byte one = (byte) (framedata.isFin() ? -128 : 0);
+ one |= optcode;
+ if (framedata.isRSV1()) {
+ one |= getRSVByte(1);
+ }
+ if (framedata.isRSV2()) {
+ one |= getRSVByte(2);
+ }
+ if (framedata.isRSV3()) {
+ one |= getRSVByte(3);
+ }
+ buf.put(one);
+ byte[] payloadlengthbytes = toByteArray(mes.remaining(), sizebytes);
+ assert (payloadlengthbytes.length == sizebytes);
+
+ if (sizebytes == 1) {
+ buf.put((byte) (payloadlengthbytes[0] | getMaskByte(mask)));
+ } else if (sizebytes == 2) {
+ buf.put((byte) ((byte) 126 | getMaskByte(mask)));
+ buf.put(payloadlengthbytes);
+ } else if (sizebytes == 8) {
+ buf.put((byte) ((byte) 127 | getMaskByte(mask)));
+ buf.put(payloadlengthbytes);
+ } else {
+ throw new IllegalStateException("Size representation not supported/specified");
+ }
+ if (mask) {
+ ByteBuffer maskkey = ByteBuffer.allocate(4);
+ maskkey.putInt(reuseableRandom.nextInt());
+ buf.put(maskkey.array());
+ for (int i = 0; mes.hasRemaining(); i++) {
+ buf.put((byte) (mes.get() ^ maskkey.get(i % 4)));
+ }
+ } else {
+ buf.put(mes);
+ //Reset the position of the bytebuffer e.g. for additional use
+ mes.flip();
+ }
+ assert (buf.remaining() == 0) : buf.remaining();
+ buf.flip();
+ return buf;
+ }
+
+ private Framedata translateSingleFrame(ByteBuffer buffer)
+ throws IncompleteException, InvalidDataException {
+ if (buffer == null) {
+ throw new IllegalArgumentException();
+ }
+ int maxpacketsize = buffer.remaining();
+ int realpacketsize = 2;
+ translateSingleFrameCheckPacketSize(maxpacketsize, realpacketsize);
+ byte b1 = buffer.get(/*0*/);
+ boolean fin = b1 >> 8 != 0;
+ boolean rsv1 = (b1 & 0x40) != 0;
+ boolean rsv2 = (b1 & 0x20) != 0;
+ boolean rsv3 = (b1 & 0x10) != 0;
+ byte b2 = buffer.get(/*1*/);
+ boolean mask = (b2 & -128) != 0;
+ int payloadlength = (byte) (b2 & ~(byte) 128);
+ Opcode optcode = toOpcode((byte) (b1 & 15));
+
+ if (!(payloadlength >= 0 && payloadlength <= 125)) {
+ TranslatedPayloadMetaData payloadData = translateSingleFramePayloadLength(buffer, optcode,
+ payloadlength, maxpacketsize, realpacketsize);
+ payloadlength = payloadData.getPayloadLength();
+ realpacketsize = payloadData.getRealPackageSize();
+ }
+ translateSingleFrameCheckLengthLimit(payloadlength);
+ realpacketsize += (mask ? 4 : 0);
+ realpacketsize += payloadlength;
+ translateSingleFrameCheckPacketSize(maxpacketsize, realpacketsize);
+
+ ByteBuffer payload = ByteBuffer.allocate(checkAlloc(payloadlength));
+ if (mask) {
+ byte[] maskskey = new byte[4];
+ buffer.get(maskskey);
+ for (int i = 0; i < payloadlength; i++) {
+ payload.put((byte) (buffer.get(/*payloadstart + i*/) ^ maskskey[i % 4]));
+ }
+ } else {
+ payload.put(buffer.array(), buffer.position(), payload.limit());
+ buffer.position(buffer.position() + payload.limit());
+ }
+
+ FramedataImpl1 frame = FramedataImpl1.get(optcode);
+ frame.setFin(fin);
+ frame.setRSV1(rsv1);
+ frame.setRSV2(rsv2);
+ frame.setRSV3(rsv3);
+ payload.flip();
+ frame.setPayload(payload);
+ if (frame.getOpcode() != Opcode.CONTINUOUS) {
+ // Prioritize the negotiated extension
+ if (frame.isRSV1() || frame.isRSV2() || frame.isRSV3()) {
+ currentDecodingExtension = getExtension();
+ } else {
+ // No encoded message, so we can use the default one
+ currentDecodingExtension = defaultExtension;
+ }
+ }
+ if (currentDecodingExtension == null) {
+ currentDecodingExtension = defaultExtension;
+ }
+ currentDecodingExtension.isFrameValid(frame);
+ currentDecodingExtension.decodeFrame(frame);
+ if (log.isTraceEnabled()) {
+ log.trace("afterDecoding({}): {}", frame.getPayloadData().remaining(),
+ (frame.getPayloadData().remaining() > 1000 ? "too big to display"
+ : new String(frame.getPayloadData().array())));
+ }
+ frame.isValid();
+ return frame;
+ }
+
+ /**
+ * Translate the buffer depending when it has an extended payload length (126 or 127)
+ *
+ * @param buffer the buffer to read from
+ * @param optcode the decoded optcode
+ * @param oldPayloadlength the old payload length
+ * @param maxpacketsize the max packet size allowed
+ * @param oldRealpacketsize the real packet size
+ * @return the new payload data containing new payload length and new packet size
+ * @throws InvalidFrameException thrown if a control frame has an invalid length
+ * @throws IncompleteException if the maxpacketsize is smaller than the realpackagesize
+ * @throws LimitExceededException if the payload length is to big
+ */
+ private TranslatedPayloadMetaData translateSingleFramePayloadLength(ByteBuffer buffer,
+ Opcode optcode, int oldPayloadlength, int maxpacketsize, int oldRealpacketsize)
+ throws InvalidFrameException, IncompleteException, LimitExceededException {
+ int payloadlength = oldPayloadlength;
+ int realpacketsize = oldRealpacketsize;
+ if (optcode == Opcode.PING || optcode == Opcode.PONG || optcode == Opcode.CLOSING) {
+ log.trace("Invalid frame: more than 125 octets");
+ throw new InvalidFrameException("more than 125 octets");
+ }
+ if (payloadlength == 126) {
+ realpacketsize += 2; // additional length bytes
+ translateSingleFrameCheckPacketSize(maxpacketsize, realpacketsize);
+ byte[] sizebytes = new byte[3];
+ sizebytes[1] = buffer.get(/*1 + 1*/);
+ sizebytes[2] = buffer.get(/*1 + 2*/);
+ payloadlength = new BigInteger(sizebytes).intValue();
+ } else {
+ realpacketsize += 8; // additional length bytes
+ translateSingleFrameCheckPacketSize(maxpacketsize, realpacketsize);
+ byte[] bytes = new byte[8];
+ for (int i = 0; i < 8; i++) {
+ bytes[i] = buffer.get(/*1 + i*/);
+ }
+ long length = new BigInteger(bytes).longValue();
+ translateSingleFrameCheckLengthLimit(length);
+ payloadlength = (int) length;
+ }
+ return new TranslatedPayloadMetaData(payloadlength, realpacketsize);
+ }
+
+ /**
+ * Check if the frame size exceeds the allowed limit
+ *
+ * @param length the current payload length
+ * @throws LimitExceededException if the payload length is to big
+ */
+ private void translateSingleFrameCheckLengthLimit(long length) throws LimitExceededException {
+ if (length > Integer.MAX_VALUE) {
+ log.trace("Limit exedeed: Payloadsize is to big...");
+ throw new LimitExceededException("Payloadsize is to big...");
+ }
+ if (length > maxFrameSize) {
+ log.trace("Payload limit reached. Allowed: {} Current: {}", maxFrameSize, length);
+ throw new LimitExceededException("Payload limit reached.", maxFrameSize);
+ }
+ if (length < 0) {
+ log.trace("Limit underflow: Payloadsize is to little...");
+ throw new LimitExceededException("Payloadsize is to little...");
+ }
+ }
+
+ /**
+ * Check if the max packet size is smaller than the real packet size
+ *
+ * @param maxpacketsize the max packet size
+ * @param realpacketsize the real packet size
+ * @throws IncompleteException if the maxpacketsize is smaller than the realpackagesize
+ */
+ private void translateSingleFrameCheckPacketSize(int maxpacketsize, int realpacketsize)
+ throws IncompleteException {
+ if (maxpacketsize < realpacketsize) {
+ log.trace("Incomplete frame: maxpacketsize < realpacketsize");
+ throw new IncompleteException(realpacketsize);
+ }
+ }
+
+ /**
+ * Get a byte that can set RSV bits when OR(|)'d. 0 1 2 3 4 5 6 7 +-+-+-+-+-------+ |F|R|R|R|
+ * opcode| |I|S|S|S| (4) | |N|V|V|V| | | |1|2|3| |
+ *
+ * @param rsv Can only be {0, 1, 2, 3}
+ * @return byte that represents which RSV bit is set.
+ */
+ private byte getRSVByte(int rsv) {
+ switch (rsv) {
+ case 1 : // 0100 0000
+ return 0x40;
+ case 2 : // 0010 0000
+ return 0x20;
+ case 3 : // 0001 0000
+ return 0x10;
+ default:
+ return 0;
+ }
+ }
+
+ /**
+ * Get the mask byte if existing
+ *
+ * @param mask is mask active or not
+ * @return -128 for true, 0 for false
+ */
+ private byte getMaskByte(boolean mask) {
+ return mask ? (byte) -128 : 0;
+ }
+
+ /**
+ * Get the size bytes for the byte buffer
+ *
+ * @param mes the current buffer
+ * @return the size bytes
+ */
+ private int getSizeBytes(ByteBuffer mes) {
+ if (mes.remaining() <= 125) {
+ return 1;
+ } else if (mes.remaining() <= 65535) {
+ return 2;
+ }
+ return 8;
+ }
+
+ @Override
+ public List translateFrame(ByteBuffer buffer) throws InvalidDataException {
+ while (true) {
+ List frames = new LinkedList<>();
+ Framedata cur;
+ if (incompleteframe != null) {
+ // complete an incomplete frame
+ try {
+ buffer.mark();
+ int availableNextByteCount = buffer.remaining();// The number of bytes received
+ int expectedNextByteCount = incompleteframe
+ .remaining();// The number of bytes to complete the incomplete frame
+
+ if (expectedNextByteCount > availableNextByteCount) {
+ // did not receive enough bytes to complete the frame
+ incompleteframe.put(buffer.array(), buffer.position(), availableNextByteCount);
+ buffer.position(buffer.position() + availableNextByteCount);
+ return Collections.emptyList();
+ }
+ incompleteframe.put(buffer.array(), buffer.position(), expectedNextByteCount);
+ buffer.position(buffer.position() + expectedNextByteCount);
+ cur = translateSingleFrame((ByteBuffer) incompleteframe.duplicate().position(0));
+ frames.add(cur);
+ incompleteframe = null;
+ } catch (IncompleteException e) {
+ // extending as much as suggested
+ ByteBuffer extendedframe = ByteBuffer.allocate(checkAlloc(e.getPreferredSize()));
+ assert (extendedframe.limit() > incompleteframe.limit());
+ incompleteframe.rewind();
+ extendedframe.put(incompleteframe);
+ incompleteframe = extendedframe;
+ continue;
+ }
+ }
+
+ // Read as much as possible full frames
+ while (buffer.hasRemaining()) {
+ buffer.mark();
+ try {
+ cur = translateSingleFrame(buffer);
+ frames.add(cur);
+ } catch (IncompleteException e) {
+ // remember the incomplete data
+ buffer.reset();
+ int pref = e.getPreferredSize();
+ incompleteframe = ByteBuffer.allocate(checkAlloc(pref));
+ incompleteframe.put(buffer);
+ break;
+ }
+ }
+ return frames;
+ }
+ }
+
+ @Override
+ public List createFrames(ByteBuffer binary, boolean mask) {
+ BinaryFrame curframe = new BinaryFrame();
+ curframe.setPayload(binary);
+ curframe.setTransferemasked(mask);
+ try {
+ curframe.isValid();
+ } catch (InvalidDataException e) {
+ throw new NotSendableException(e);
+ }
+ return Collections.singletonList((Framedata) curframe);
+ }
+
+ @Override
+ public List createFrames(String text, boolean mask) {
+ TextFrame curframe = new TextFrame();
+ curframe.setPayload(ByteBuffer.wrap(Charsetfunctions.utf8Bytes(text)));
+ curframe.setTransferemasked(mask);
+ try {
+ curframe.isValid();
+ } catch (InvalidDataException e) {
+ throw new NotSendableException(e);
+ }
+ return Collections.singletonList((Framedata) curframe);
+ }
+
+ @Override
+ public void reset() {
+ incompleteframe = null;
+ if (negotiatedExtension != null) {
+ negotiatedExtension.reset();
+ }
+ negotiatedExtension = new DefaultExtension();
+ protocol = null;
+ }
+
+ /**
+ * Generate a date for for the date-header
+ *
+ * @return the server time
+ */
+ private String getServerTime() {
+ Calendar calendar = Calendar.getInstance();
+ SimpleDateFormat dateFormat = new SimpleDateFormat(
+ "EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
+ dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
+ return dateFormat.format(calendar.getTime());
+ }
+
+ /**
+ * Generate a final key from a input string
+ *
+ * @param in the input string
+ * @return a final key
+ */
+ private String generateFinalKey(String in) {
+ String seckey = in.trim();
+ String acc = seckey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+ MessageDigest sh1;
+ try {
+ sh1 = MessageDigest.getInstance("SHA1");
+ } catch (NoSuchAlgorithmException e) {
+ throw new IllegalStateException(e);
+ }
+ return Base64.encodeBytes(sh1.digest(acc.getBytes()));
+ }
+
+ private byte[] toByteArray(long val, int bytecount) {
+ byte[] buffer = new byte[bytecount];
+ int highest = 8 * bytecount - 8;
+ for (int i = 0; i < bytecount; i++) {
+ buffer[i] = (byte) (val >>> (highest - 8 * i));
+ }
+ return buffer;
+ }
+
+
+ private byte fromOpcode(Opcode opcode) {
+ if (opcode == Opcode.CONTINUOUS) {
+ return 0;
+ } else if (opcode == Opcode.TEXT) {
+ return 1;
+ } else if (opcode == Opcode.BINARY) {
+ return 2;
+ } else if (opcode == Opcode.CLOSING) {
+ return 8;
+ } else if (opcode == Opcode.PING) {
+ return 9;
+ } else if (opcode == Opcode.PONG) {
+ return 10;
+ }
+ throw new IllegalArgumentException("Don't know how to handle " + opcode.toString());
+ }
+
+ private Opcode toOpcode(byte opcode) throws InvalidFrameException {
+ switch (opcode) {
+ case 0:
+ return Opcode.CONTINUOUS;
+ case 1:
+ return Opcode.TEXT;
+ case 2:
+ return Opcode.BINARY;
+ // 3-7 are not yet defined
+ case 8:
+ return Opcode.CLOSING;
+ case 9:
+ return Opcode.PING;
+ case 10:
+ return Opcode.PONG;
+ // 11-15 are not yet defined
+ default:
+ throw new InvalidFrameException("Unknown opcode " + (short) opcode);
+ }
+ }
+
+ @Override
+ public void processFrame(WebSocketImpl webSocketImpl, Framedata frame)
+ throws InvalidDataException {
+ Opcode curop = frame.getOpcode();
+ if (curop == Opcode.CLOSING) {
+ processFrameClosing(webSocketImpl, frame);
+ } else if (curop == Opcode.PING) {
+ webSocketImpl.getWebSocketListener().onWebsocketPing(webSocketImpl, frame);
+ } else if (curop == Opcode.PONG) {
+ webSocketImpl.updateLastPong();
+ webSocketImpl.getWebSocketListener().onWebsocketPong(webSocketImpl, frame);
+ } else if (!frame.isFin() || curop == Opcode.CONTINUOUS) {
+ processFrameContinuousAndNonFin(webSocketImpl, frame, curop);
+ } else if (currentContinuousFrame != null) {
+ log.error("Protocol error: Continuous frame sequence not completed.");
+ throw new InvalidDataException(CloseFrame.PROTOCOL_ERROR,
+ "Continuous frame sequence not completed.");
+ } else if (curop == Opcode.TEXT) {
+ processFrameText(webSocketImpl, frame);
+ } else if (curop == Opcode.BINARY) {
+ processFrameBinary(webSocketImpl, frame);
+ } else {
+ log.error("non control or continious frame expected");
+ throw new InvalidDataException(CloseFrame.PROTOCOL_ERROR,
+ "non control or continious frame expected");
+ }
+ }
+
+ /**
+ * Process the frame if it is a continuous frame or the fin bit is not set
+ *
+ * @param webSocketImpl the websocket implementation to use
+ * @param frame the current frame
+ * @param curop the current Opcode
+ * @throws InvalidDataException if there is a protocol error
+ */
+ private void processFrameContinuousAndNonFin(WebSocketImpl webSocketImpl, Framedata frame,
+ Opcode curop) throws InvalidDataException {
+ if (curop != Opcode.CONTINUOUS) {
+ processFrameIsNotFin(frame);
+ } else if (frame.isFin()) {
+ processFrameIsFin(webSocketImpl, frame);
+ } else if (currentContinuousFrame == null) {
+ log.error("Protocol error: Continuous frame sequence was not started.");
+ throw new InvalidDataException(CloseFrame.PROTOCOL_ERROR,
+ "Continuous frame sequence was not started.");
+ }
+ //Check if the whole payload is valid utf8, when the opcode indicates a text
+ if (curop == Opcode.TEXT && !Charsetfunctions.isValidUTF8(frame.getPayloadData())) {
+ log.error("Protocol error: Payload is not UTF8");
+ throw new InvalidDataException(CloseFrame.NO_UTF8);
+ }
+ //Checking if the current continuous frame contains a correct payload with the other frames combined
+ if (curop == Opcode.CONTINUOUS && currentContinuousFrame != null) {
+ addToBufferList(frame.getPayloadData());
+ }
+ }
+
+ /**
+ * Process the frame if it is a binary frame
+ *
+ * @param webSocketImpl the websocket impl
+ * @param frame the frame
+ */
+ private void processFrameBinary(WebSocketImpl webSocketImpl, Framedata frame) {
+ try {
+ webSocketImpl.getWebSocketListener()
+ .onWebsocketMessage(webSocketImpl, frame.getPayloadData());
+ } catch (RuntimeException e) {
+ logRuntimeException(webSocketImpl, e);
+ }
+ }
+
+ /**
+ * Log the runtime exception to the specific WebSocketImpl
+ *
+ * @param webSocketImpl the implementation of the websocket
+ * @param e the runtime exception
+ */
+ private void logRuntimeException(WebSocketImpl webSocketImpl, RuntimeException e) {
+ log.error("Runtime exception during onWebsocketMessage", e);
+ webSocketImpl.getWebSocketListener().onWebsocketError(webSocketImpl, e);
+ }
+
+ /**
+ * Process the frame if it is a text frame
+ *
+ * @param webSocketImpl the websocket impl
+ * @param frame the frame
+ */
+ private void processFrameText(WebSocketImpl webSocketImpl, Framedata frame)
+ throws InvalidDataException {
+ try {
+ webSocketImpl.getWebSocketListener()
+ .onWebsocketMessage(webSocketImpl, Charsetfunctions.stringUtf8(frame.getPayloadData()));
+ } catch (RuntimeException e) {
+ logRuntimeException(webSocketImpl, e);
+ }
+ }
+
+ /**
+ * Process the frame if it is the last frame
+ *
+ * @param webSocketImpl the websocket impl
+ * @param frame the frame
+ * @throws InvalidDataException if there is a protocol error
+ */
+ private void processFrameIsFin(WebSocketImpl webSocketImpl, Framedata frame)
+ throws InvalidDataException {
+ if (currentContinuousFrame == null) {
+ log.trace("Protocol error: Previous continuous frame sequence not completed.");
+ throw new InvalidDataException(CloseFrame.PROTOCOL_ERROR,
+ "Continuous frame sequence was not started.");
+ }
+ addToBufferList(frame.getPayloadData());
+ checkBufferLimit();
+ if (currentContinuousFrame.getOpcode() == Opcode.TEXT) {
+ ((FramedataImpl1) currentContinuousFrame).setPayload(getPayloadFromByteBufferList());
+ ((FramedataImpl1) currentContinuousFrame).isValid();
+ try {
+ webSocketImpl.getWebSocketListener().onWebsocketMessage(webSocketImpl,
+ Charsetfunctions.stringUtf8(currentContinuousFrame.getPayloadData()));
+ } catch (RuntimeException e) {
+ logRuntimeException(webSocketImpl, e);
+ }
+ } else if (currentContinuousFrame.getOpcode() == Opcode.BINARY) {
+ ((FramedataImpl1) currentContinuousFrame).setPayload(getPayloadFromByteBufferList());
+ ((FramedataImpl1) currentContinuousFrame).isValid();
+ try {
+ webSocketImpl.getWebSocketListener()
+ .onWebsocketMessage(webSocketImpl, currentContinuousFrame.getPayloadData());
+ } catch (RuntimeException e) {
+ logRuntimeException(webSocketImpl, e);
+ }
+ }
+ currentContinuousFrame = null;
+ clearBufferList();
+ }
+
+ /**
+ * Process the frame if it is not the last frame
+ *
+ * @param frame the frame
+ * @throws InvalidDataException if there is a protocol error
+ */
+ private void processFrameIsNotFin(Framedata frame) throws InvalidDataException {
+ if (currentContinuousFrame != null) {
+ log.trace("Protocol error: Previous continuous frame sequence not completed.");
+ throw new InvalidDataException(CloseFrame.PROTOCOL_ERROR,
+ "Previous continuous frame sequence not completed.");
+ }
+ currentContinuousFrame = frame;
+ addToBufferList(frame.getPayloadData());
+ checkBufferLimit();
+ }
+
+ /**
+ * Process the frame if it is a closing frame
+ *
+ * @param webSocketImpl the websocket impl
+ * @param frame the frame
+ */
+ private void processFrameClosing(WebSocketImpl webSocketImpl, Framedata frame) {
+ int code = CloseFrame.NOCODE;
+ String reason = "";
+ if (frame instanceof CloseFrame) {
+ CloseFrame cf = (CloseFrame) frame;
+ code = cf.getCloseCode();
+ reason = cf.getMessage();
+ }
+ if (webSocketImpl.getReadyState() == ReadyState.CLOSING) {
+ // complete the close handshake by disconnecting
+ webSocketImpl.closeConnection(code, reason, true);
+ } else {
+ // echo close handshake
+ if (getCloseHandshakeType() == CloseHandshakeType.TWOWAY) {
+ webSocketImpl.close(code, reason, true);
+ } else {
+ webSocketImpl.flushAndClose(code, reason, false);
+ }
+ }
+ }
+
+ /**
+ * Clear the current bytebuffer list
+ */
+ private void clearBufferList() {
+ synchronized (byteBufferList) {
+ byteBufferList.clear();
+ }
+ }
+
+ /**
+ * Add a payload to the current bytebuffer list
+ *
+ * @param payloadData the new payload
+ */
+ private void addToBufferList(ByteBuffer payloadData) {
+ synchronized (byteBufferList) {
+ byteBufferList.add(payloadData);
+ }
+ }
+
+ /**
+ * Check the current size of the buffer and throw an exception if the size is bigger than the max
+ * allowed frame size
+ *
+ * @throws LimitExceededException if the current size is bigger than the allowed size
+ */
+ private void checkBufferLimit() throws LimitExceededException {
+ long totalSize = getByteBufferListSize();
+ if (totalSize > maxFrameSize) {
+ clearBufferList();
+ log.trace("Payload limit reached. Allowed: {} Current: {}", maxFrameSize, totalSize);
+ throw new LimitExceededException(maxFrameSize);
+ }
+ }
+
+ @Override
+ public CloseHandshakeType getCloseHandshakeType() {
+ return CloseHandshakeType.TWOWAY;
+ }
+
+ @Override
+ public String toString() {
+ String result = super.toString();
+ if (getExtension() != null) {
+ result += " extension: " + getExtension().toString();
+ }
+ if (getProtocol() != null) {
+ result += " protocol: " + getProtocol().toString();
+ }
+ result += " max frame size: " + this.maxFrameSize;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ Draft_6455 that = (Draft_6455) o;
+
+ if (maxFrameSize != that.getMaxFrameSize()) {
+ return false;
+ }
+ if (negotiatedExtension != null ? !negotiatedExtension.equals(that.getExtension()) : that.getExtension() != null) {
+ return false;
+ }
+ return protocol != null ? protocol.equals(that.getProtocol()) : that.getProtocol() == null;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = negotiatedExtension != null ? negotiatedExtension.hashCode() : 0;
+ result = 31 * result + (protocol != null ? protocol.hashCode() : 0);
+ result = 31 * result + (maxFrameSize ^ (maxFrameSize >>> 32));
+ return result;
+ }
+
+ /**
+ * Method to generate a full bytebuffer out of all the fragmented frame payload
+ *
+ * @return a bytebuffer containing all the data
+ * @throws LimitExceededException will be thrown when the totalSize is bigger then
+ * Integer.MAX_VALUE due to not being able to allocate more
+ */
+ private ByteBuffer getPayloadFromByteBufferList() throws LimitExceededException {
+ long totalSize = 0;
+ ByteBuffer resultingByteBuffer;
+ synchronized (byteBufferList) {
+ for (ByteBuffer buffer : byteBufferList) {
+ totalSize += buffer.limit();
+ }
+ checkBufferLimit();
+ resultingByteBuffer = ByteBuffer.allocate((int) totalSize);
+ for (ByteBuffer buffer : byteBufferList) {
+ resultingByteBuffer.put(buffer);
+ }
+ }
+ resultingByteBuffer.flip();
+ return resultingByteBuffer;
+ }
+
+ /**
+ * Get the current size of the resulting bytebuffer in the bytebuffer list
+ *
+ * @return the size as long (to not get an integer overflow)
+ */
+ private long getByteBufferListSize() {
+ long totalSize = 0;
+ synchronized (byteBufferList) {
+ for (ByteBuffer buffer : byteBufferList) {
+ totalSize += buffer.limit();
+ }
+ }
+ return totalSize;
+ }
+
+ private class TranslatedPayloadMetaData {
+
+ private int payloadLength;
+ private int realPackageSize;
+
+ private int getPayloadLength() {
+ return payloadLength;
+ }
+
+ private int getRealPackageSize() {
+ return realPackageSize;
+ }
+
+ TranslatedPayloadMetaData(int newPayloadLength, int newRealPackageSize) {
+ this.payloadLength = newPayloadLength;
+ this.realPackageSize = newRealPackageSize;
+ }
+ }
}
diff --git a/src/main/java/org/java_websocket/drafts/package-info.java b/src/main/java/org/java_websocket/drafts/package-info.java
index b6ca2a599..13114293e 100644
--- a/src/main/java/org/java_websocket/drafts/package-info.java
+++ b/src/main/java/org/java_websocket/drafts/package-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
diff --git a/src/main/java/org/java_websocket/enums/CloseHandshakeType.java b/src/main/java/org/java_websocket/enums/CloseHandshakeType.java
new file mode 100644
index 000000000..0bd1f94d5
--- /dev/null
+++ b/src/main/java/org/java_websocket/enums/CloseHandshakeType.java
@@ -0,0 +1,8 @@
+package org.java_websocket.enums;
+
+/**
+ * Enum which represents type of handshake is required for a close
+ */
+public enum CloseHandshakeType {
+ NONE, ONEWAY, TWOWAY
+}
\ No newline at end of file
diff --git a/src/main/java/org/java_websocket/enums/HandshakeState.java b/src/main/java/org/java_websocket/enums/HandshakeState.java
new file mode 100644
index 000000000..c87750601
--- /dev/null
+++ b/src/main/java/org/java_websocket/enums/HandshakeState.java
@@ -0,0 +1,15 @@
+package org.java_websocket.enums;
+
+/**
+ * Enum which represents the states a handshake may be in
+ */
+public enum HandshakeState {
+ /**
+ * Handshake matched this Draft successfully
+ */
+ MATCHED,
+ /**
+ * Handshake is does not match this Draft
+ */
+ NOT_MATCHED
+}
\ No newline at end of file
diff --git a/src/main/java/org/java_websocket/enums/Opcode.java b/src/main/java/org/java_websocket/enums/Opcode.java
new file mode 100644
index 000000000..cc65e31a0
--- /dev/null
+++ b/src/main/java/org/java_websocket/enums/Opcode.java
@@ -0,0 +1,9 @@
+package org.java_websocket.enums;
+
+/**
+ * Enum which contains the different valid opcodes
+ */
+public enum Opcode {
+ CONTINUOUS, TEXT, BINARY, PING, PONG, CLOSING
+ // more to come
+}
\ No newline at end of file
diff --git a/src/main/java/org/java_websocket/enums/ReadyState.java b/src/main/java/org/java_websocket/enums/ReadyState.java
new file mode 100644
index 000000000..15bd5cc8d
--- /dev/null
+++ b/src/main/java/org/java_websocket/enums/ReadyState.java
@@ -0,0 +1,8 @@
+package org.java_websocket.enums;
+
+/**
+ * Enum which represents the state a websocket may be in
+ */
+public enum ReadyState {
+ NOT_YET_CONNECTED, OPEN, CLOSING, CLOSED
+}
\ No newline at end of file
diff --git a/src/main/java/org/java_websocket/enums/Role.java b/src/main/java/org/java_websocket/enums/Role.java
new file mode 100644
index 000000000..8a057a308
--- /dev/null
+++ b/src/main/java/org/java_websocket/enums/Role.java
@@ -0,0 +1,8 @@
+package org.java_websocket.enums;
+
+/**
+ * Enum which represents the states a websocket may be in
+ */
+public enum Role {
+ CLIENT, SERVER
+}
\ No newline at end of file
diff --git a/src/test/java/org/java_websocket/drafts/AllDraftTests.java b/src/main/java/org/java_websocket/enums/package-info.java
similarity index 78%
rename from src/test/java/org/java_websocket/drafts/AllDraftTests.java
rename to src/main/java/org/java_websocket/enums/package-info.java
index 8648667a0..a5c997ea9 100644
--- a/src/test/java/org/java_websocket/drafts/AllDraftTests.java
+++ b/src/main/java/org/java_websocket/enums/package-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -23,17 +23,7 @@
* OTHER DEALINGS IN THE SOFTWARE.
*/
-package org.java_websocket.drafts;
-import org.junit.runner.RunWith;
-import org.junit.runners.Suite;
-
-
-@RunWith(Suite.class)
-@Suite.SuiteClasses({
- org.java_websocket.drafts.Draft_6455Test.class
-})
/**
- * Start all tests for drafts
+ * This package encapsulates all enums.
*/
-public class AllDraftTests {
-}
+package org.java_websocket.enums;
\ No newline at end of file
diff --git a/src/main/java/org/java_websocket/exceptions/IncompleteException.java b/src/main/java/org/java_websocket/exceptions/IncompleteException.java
index 81fa2c098..d3e83834d 100644
--- a/src/main/java/org/java_websocket/exceptions/IncompleteException.java
+++ b/src/main/java/org/java_websocket/exceptions/IncompleteException.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -28,31 +28,33 @@
/**
* Exception which indicates that the frame is not yet complete
*/
-public class IncompleteException extends Throwable {
+public class IncompleteException extends Exception {
- /**
- * It's Serializable.
- */
- private static final long serialVersionUID = 7330519489840500997L;
+ /**
+ * It's Serializable.
+ */
+ private static final long serialVersionUID = 7330519489840500997L;
- /**
- * The preferred size
- */
- private int preferredSize;
+ /**
+ * The preferred size
+ */
+ private final int preferredSize;
- /**
- * Constructor for the preferred size of a frame
- * @param preferredSize the preferred size of a frame
- */
- public IncompleteException( int preferredSize ) {
- this.preferredSize = preferredSize;
- }
+ /**
+ * Constructor for the preferred size of a frame
+ *
+ * @param preferredSize the preferred size of a frame
+ */
+ public IncompleteException(int preferredSize) {
+ this.preferredSize = preferredSize;
+ }
- /**
- * Getter for the preferredSize
- * @return the value of the preferred size
- */
- public int getPreferredSize() {
- return preferredSize;
- }
+ /**
+ * Getter for the preferredSize
+ *
+ * @return the value of the preferred size
+ */
+ public int getPreferredSize() {
+ return preferredSize;
+ }
}
diff --git a/src/main/java/org/java_websocket/exceptions/IncompleteHandshakeException.java b/src/main/java/org/java_websocket/exceptions/IncompleteHandshakeException.java
index 2f5b8a536..17307c379 100644
--- a/src/main/java/org/java_websocket/exceptions/IncompleteHandshakeException.java
+++ b/src/main/java/org/java_websocket/exceptions/IncompleteHandshakeException.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -26,45 +26,46 @@
package org.java_websocket.exceptions;
/**
- * exception which indicates that a incomplete handshake was recieved
+ * exception which indicates that a incomplete handshake was received
*/
public class IncompleteHandshakeException extends RuntimeException {
- /**
- * Serializable
- */
- private static final long serialVersionUID = 7906596804233893092L;
+ /**
+ * Serializable
+ */
+ private static final long serialVersionUID = 7906596804233893092L;
- /**
- * attribut which size of handshake would have been prefered
- */
- private int preferedSize;
+ /**
+ * attribute which size of handshake would have been preferred
+ */
+ private final int preferredSize;
- /**
- * constructor for a IncompleteHandshakeException
- *
- * @param preferedSize the prefered size
- */
- public IncompleteHandshakeException(int preferedSize) {
- this.preferedSize = preferedSize;
- }
+ /**
+ * constructor for a IncompleteHandshakeException
+ *
+ *
+ * @param preferredSize the preferred size
+ */
+ public IncompleteHandshakeException(int preferredSize) {
+ this.preferredSize = preferredSize;
+ }
- /**
- * constructor for a IncompleteHandshakeException
- *
- * preferedSize will be 0
- */
- public IncompleteHandshakeException() {
- this.preferedSize = 0;
- }
+ /**
+ * constructor for a IncompleteHandshakeException
+ *
+ * preferredSize will be 0
+ */
+ public IncompleteHandshakeException() {
+ this.preferredSize = 0;
+ }
- /**
- * Getter preferedSize
- *
- * @return the preferedSize
- */
- public int getPreferedSize() {
- return preferedSize;
- }
+ /**
+ * Getter preferredSize
+ *
+ * @return the preferredSize
+ */
+ public int getPreferredSize() {
+ return preferredSize;
+ }
}
diff --git a/src/main/java/org/java_websocket/exceptions/InvalidDataException.java b/src/main/java/org/java_websocket/exceptions/InvalidDataException.java
index f6342146b..c34c8c941 100644
--- a/src/main/java/org/java_websocket/exceptions/InvalidDataException.java
+++ b/src/main/java/org/java_websocket/exceptions/InvalidDataException.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -26,70 +26,70 @@
package org.java_websocket.exceptions;
/**
- * exception which indicates that a invalid data was recieved
+ * exception which indicates that a invalid data was received
*/
public class InvalidDataException extends Exception {
- /**
- * Serializable
- */
- private static final long serialVersionUID = 3731842424390998726L;
+ /**
+ * Serializable
+ */
+ private static final long serialVersionUID = 3731842424390998726L;
- /**
- * attribut which closecode will be returned
- */
- private int closecode;
+ /**
+ * attribute which closecode will be returned
+ */
+ private final int closecode;
- /**
- * constructor for a InvalidDataException
- *
- * @param closecode the closecode which will be returned
- */
- public InvalidDataException(int closecode) {
- this.closecode = closecode;
- }
+ /**
+ * constructor for a InvalidDataException
+ *
+ * @param closecode the closecode which will be returned
+ */
+ public InvalidDataException(int closecode) {
+ this.closecode = closecode;
+ }
- /**
- * constructor for a InvalidDataException.
- *
- * @param closecode the closecode which will be returned.
- * @param s the detail message.
- */
- public InvalidDataException(int closecode, String s) {
- super(s);
- this.closecode = closecode;
- }
+ /**
+ * constructor for a InvalidDataException.
+ *
+ * @param closecode the closecode which will be returned.
+ * @param s the detail message.
+ */
+ public InvalidDataException(int closecode, String s) {
+ super(s);
+ this.closecode = closecode;
+ }
- /**
- * constructor for a InvalidDataException.
- *
- * @param closecode the closecode which will be returned.
- * @param t the throwable causing this exception.
- */
- public InvalidDataException(int closecode, Throwable t) {
- super(t);
- this.closecode = closecode;
- }
+ /**
+ * constructor for a InvalidDataException.
+ *
+ * @param closecode the closecode which will be returned.
+ * @param t the throwable causing this exception.
+ */
+ public InvalidDataException(int closecode, Throwable t) {
+ super(t);
+ this.closecode = closecode;
+ }
- /**
- * constructor for a InvalidDataException.
- *
- * @param closecode the closecode which will be returned.
- * @param s the detail message.
- * @param t the throwable causing this exception.
- */
- public InvalidDataException(int closecode, String s, Throwable t) {
- super(s, t);
- this.closecode = closecode;
- }
+ /**
+ * constructor for a InvalidDataException.
+ *
+ * @param closecode the closecode which will be returned.
+ * @param s the detail message.
+ * @param t the throwable causing this exception.
+ */
+ public InvalidDataException(int closecode, String s, Throwable t) {
+ super(s, t);
+ this.closecode = closecode;
+ }
- /**
- * Getter closecode
- *
- * @return the closecode
- */
- public int getCloseCode() {
- return closecode;
- }
+ /**
+ * Getter closecode
+ *
+ * @return the closecode
+ */
+ public int getCloseCode() {
+ return closecode;
+ }
}
diff --git a/src/main/java/org/java_websocket/exceptions/InvalidEncodingException.java b/src/main/java/org/java_websocket/exceptions/InvalidEncodingException.java
new file mode 100644
index 000000000..8fdbd1a38
--- /dev/null
+++ b/src/main/java/org/java_websocket/exceptions/InvalidEncodingException.java
@@ -0,0 +1,37 @@
+package org.java_websocket.exceptions;
+
+import java.io.UnsupportedEncodingException;
+
+/**
+ * The Character Encoding is not supported.
+ *
+ * @since 1.4.0
+ */
+public class InvalidEncodingException extends RuntimeException {
+
+ /**
+ * attribute for the encoding exception
+ */
+ private final UnsupportedEncodingException encodingException;
+
+ /**
+ * constructor for InvalidEncodingException
+ *
+ * @param encodingException the cause for this exception
+ */
+ public InvalidEncodingException(UnsupportedEncodingException encodingException) {
+ if (encodingException == null) {
+ throw new IllegalArgumentException();
+ }
+ this.encodingException = encodingException;
+ }
+
+ /**
+ * Get the exception which includes more information on the unsupported encoding
+ *
+ * @return an UnsupportedEncodingException
+ */
+ public UnsupportedEncodingException getEncodingException() {
+ return encodingException;
+ }
+}
diff --git a/src/main/java/org/java_websocket/exceptions/InvalidFrameException.java b/src/main/java/org/java_websocket/exceptions/InvalidFrameException.java
index b682a77d1..7de2034da 100644
--- a/src/main/java/org/java_websocket/exceptions/InvalidFrameException.java
+++ b/src/main/java/org/java_websocket/exceptions/InvalidFrameException.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -28,55 +28,55 @@
import org.java_websocket.framing.CloseFrame;
/**
- * exception which indicates that a invalid frame was recieved (CloseFrame.PROTOCOL_ERROR)
+ * exception which indicates that a invalid frame was received (CloseFrame.PROTOCOL_ERROR)
*/
public class InvalidFrameException extends InvalidDataException {
- /**
- * Serializable
- */
- private static final long serialVersionUID = -9016496369828887591L;
+ /**
+ * Serializable
+ */
+ private static final long serialVersionUID = -9016496369828887591L;
- /**
- * constructor for a InvalidFrameException
- *
- * calling InvalidDataException with closecode PROTOCOL_ERROR
- */
- public InvalidFrameException() {
- super( CloseFrame.PROTOCOL_ERROR);
- }
+ /**
+ * constructor for a InvalidFrameException
+ *
+ * calling InvalidDataException with closecode PROTOCOL_ERROR
+ */
+ public InvalidFrameException() {
+ super(CloseFrame.PROTOCOL_ERROR);
+ }
- /**
- * constructor for a InvalidFrameException
- *
- * calling InvalidDataException with closecode PROTOCOL_ERROR
- *
- * @param s the detail message.
- */
- public InvalidFrameException(String s) {
- super( CloseFrame.PROTOCOL_ERROR, s);
- }
+ /**
+ * constructor for a InvalidFrameException
+ *
+ * calling InvalidDataException with closecode PROTOCOL_ERROR
+ *
+ * @param s the detail message.
+ */
+ public InvalidFrameException(String s) {
+ super(CloseFrame.PROTOCOL_ERROR, s);
+ }
- /**
- * constructor for a InvalidFrameException
- *
- * calling InvalidDataException with closecode PROTOCOL_ERROR
- *
- * @param t the throwable causing this exception.
- */
- public InvalidFrameException(Throwable t) {
- super( CloseFrame.PROTOCOL_ERROR, t);
- }
+ /**
+ * constructor for a InvalidFrameException
+ *
+ * calling InvalidDataException with closecode PROTOCOL_ERROR
+ *
+ * @param t the throwable causing this exception.
+ */
+ public InvalidFrameException(Throwable t) {
+ super(CloseFrame.PROTOCOL_ERROR, t);
+ }
- /**
- * constructor for a InvalidFrameException
- *
- * calling InvalidDataException with closecode PROTOCOL_ERROR
- *
- * @param s the detail message.
- * @param t the throwable causing this exception.
- */
- public InvalidFrameException(String s, Throwable t) {
- super( CloseFrame.PROTOCOL_ERROR, s, t);
- }
+ /**
+ * constructor for a InvalidFrameException
+ *
+ * calling InvalidDataException with closecode PROTOCOL_ERROR
+ *
+ * @param s the detail message.
+ * @param t the throwable causing this exception.
+ */
+ public InvalidFrameException(String s, Throwable t) {
+ super(CloseFrame.PROTOCOL_ERROR, s, t);
+ }
}
diff --git a/src/main/java/org/java_websocket/exceptions/InvalidHandshakeException.java b/src/main/java/org/java_websocket/exceptions/InvalidHandshakeException.java
index 782f8e167..af1fd2157 100644
--- a/src/main/java/org/java_websocket/exceptions/InvalidHandshakeException.java
+++ b/src/main/java/org/java_websocket/exceptions/InvalidHandshakeException.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -28,56 +28,56 @@
import org.java_websocket.framing.CloseFrame;
/**
- * exception which indicates that a invalid handshake was recieved (CloseFrame.PROTOCOL_ERROR)
+ * exception which indicates that a invalid handshake was received (CloseFrame.PROTOCOL_ERROR)
*/
public class InvalidHandshakeException extends InvalidDataException {
- /**
- * Serializable
- */
- private static final long serialVersionUID = -1426533877490484964L;
+ /**
+ * Serializable
+ */
+ private static final long serialVersionUID = -1426533877490484964L;
- /**
- * constructor for a InvalidHandshakeException
- *
- * calling InvalidDataException with closecode PROTOCOL_ERROR
- */
- public InvalidHandshakeException() {
- super( CloseFrame.PROTOCOL_ERROR);
- }
+ /**
+ * constructor for a InvalidHandshakeException
+ *
+ * calling InvalidDataException with closecode PROTOCOL_ERROR
+ */
+ public InvalidHandshakeException() {
+ super(CloseFrame.PROTOCOL_ERROR);
+ }
- /**
- * constructor for a InvalidHandshakeException
- *
- * calling InvalidDataException with closecode PROTOCOL_ERROR
- *
- * @param s the detail message.
- * @param t the throwable causing this exception.
- */
- public InvalidHandshakeException(String s, Throwable t) {
- super( CloseFrame.PROTOCOL_ERROR, s, t);
- }
+ /**
+ * constructor for a InvalidHandshakeException
+ *
+ * calling InvalidDataException with closecode PROTOCOL_ERROR
+ *
+ * @param s the detail message.
+ * @param t the throwable causing this exception.
+ */
+ public InvalidHandshakeException(String s, Throwable t) {
+ super(CloseFrame.PROTOCOL_ERROR, s, t);
+ }
- /**
- * constructor for a InvalidHandshakeException
- *
- * calling InvalidDataException with closecode PROTOCOL_ERROR
- *
- * @param s the detail message.
- */
- public InvalidHandshakeException(String s) {
- super( CloseFrame.PROTOCOL_ERROR, s);
- }
+ /**
+ * constructor for a InvalidHandshakeException
+ *
+ * calling InvalidDataException with closecode PROTOCOL_ERROR
+ *
+ * @param s the detail message.
+ */
+ public InvalidHandshakeException(String s) {
+ super(CloseFrame.PROTOCOL_ERROR, s);
+ }
- /**
- * constructor for a InvalidHandshakeException
- *
- * calling InvalidDataException with closecode PROTOCOL_ERROR
- *
- * @param t the throwable causing this exception.
- */
- public InvalidHandshakeException(Throwable t) {
- super( CloseFrame.PROTOCOL_ERROR, t);
- }
+ /**
+ * constructor for a InvalidHandshakeException
+ *
+ * calling InvalidDataException with closecode PROTOCOL_ERROR
+ *
+ * @param t the throwable causing this exception.
+ */
+ public InvalidHandshakeException(Throwable t) {
+ super(CloseFrame.PROTOCOL_ERROR, t);
+ }
}
diff --git a/src/main/java/org/java_websocket/exceptions/LimitExceededException.java b/src/main/java/org/java_websocket/exceptions/LimitExceededException.java
new file mode 100644
index 000000000..0d4a81825
--- /dev/null
+++ b/src/main/java/org/java_websocket/exceptions/LimitExceededException.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2010-2020 Nathan Rajlich
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package org.java_websocket.exceptions;
+
+import org.java_websocket.framing.CloseFrame;
+
+/**
+ * exception which indicates that the message limited was exceeded (CloseFrame.TOOBIG)
+ */
+public class LimitExceededException extends InvalidDataException {
+
+ /**
+ * Serializable
+ */
+ private static final long serialVersionUID = 6908339749836826785L;
+
+ /**
+ * A closer indication about the limit
+ */
+ private final int limit;
+
+ /**
+ * constructor for a LimitExceededException
+ *
+ * calling LimitExceededException with closecode TOOBIG
+ */
+ public LimitExceededException() {
+ this(Integer.MAX_VALUE);
+ }
+
+ /**
+ * constructor for a LimitExceededException
+ *
+ * calling InvalidDataException with closecode TOOBIG
+ * @param limit the allowed size which was not enough
+ */
+ public LimitExceededException(int limit) {
+ super(CloseFrame.TOOBIG);
+ this.limit = limit;
+ }
+
+ /**
+ * constructor for a LimitExceededException
+ *
+ * calling InvalidDataException with closecode TOOBIG
+ * @param s the detail message.
+ * @param limit the allowed size which was not enough
+ */
+ public LimitExceededException(String s, int limit) {
+ super(CloseFrame.TOOBIG, s);
+ this.limit = limit;
+ }
+
+ /**
+ * constructor for a LimitExceededException
+ *
+ * calling InvalidDataException with closecode TOOBIG
+ *
+ * @param s the detail message.
+ */
+ public LimitExceededException(String s) {
+ this(s, Integer.MAX_VALUE);
+ }
+
+ /**
+ * Get the limit which was hit so this exception was caused
+ *
+ * @return the limit as int
+ */
+ public int getLimit() {
+ return limit;
+ }
+}
diff --git a/src/main/java/org/java_websocket/exceptions/NotSendableException.java b/src/main/java/org/java_websocket/exceptions/NotSendableException.java
index 76e99020b..fbacca9c7 100644
--- a/src/main/java/org/java_websocket/exceptions/NotSendableException.java
+++ b/src/main/java/org/java_websocket/exceptions/NotSendableException.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -30,37 +30,37 @@
*/
public class NotSendableException extends RuntimeException {
- /**
- * Serializable
- */
- private static final long serialVersionUID = -6468967874576651628L;
+ /**
+ * Serializable
+ */
+ private static final long serialVersionUID = -6468967874576651628L;
- /**
- * constructor for a NotSendableException
- *
- * @param s the detail message.
- */
- public NotSendableException(String s) {
- super(s);
- }
+ /**
+ * constructor for a NotSendableException
+ *
+ * @param s the detail message.
+ */
+ public NotSendableException(String s) {
+ super(s);
+ }
- /**
- * constructor for a NotSendableException
- *
- * @param t the throwable causing this exception.
- */
- public NotSendableException(Throwable t) {
- super(t);
- }
+ /**
+ * constructor for a NotSendableException
+ *
+ * @param t the throwable causing this exception.
+ */
+ public NotSendableException(Throwable t) {
+ super(t);
+ }
- /**
- * constructor for a NotSendableException
- *
- * @param s the detail message.
- * @param t the throwable causing this exception.
- */
- public NotSendableException(String s, Throwable t) {
- super(s, t);
- }
+ /**
+ * constructor for a NotSendableException
+ *
+ * @param s the detail message.
+ * @param t the throwable causing this exception.
+ */
+ public NotSendableException(String s, Throwable t) {
+ super(s, t);
+ }
}
diff --git a/src/main/java/org/java_websocket/exceptions/WebsocketNotConnectedException.java b/src/main/java/org/java_websocket/exceptions/WebsocketNotConnectedException.java
index 1061bbd32..082f0bd5f 100644
--- a/src/main/java/org/java_websocket/exceptions/WebsocketNotConnectedException.java
+++ b/src/main/java/org/java_websocket/exceptions/WebsocketNotConnectedException.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -26,12 +26,12 @@
package org.java_websocket.exceptions;
/**
- * exception which indicates the websocket is not yet connected (READYSTATE.OPEN)
+ * exception which indicates the websocket is not yet connected (ReadyState.OPEN)
*/
public class WebsocketNotConnectedException extends RuntimeException {
- /**
- * Serializable
- */
- private static final long serialVersionUID = -785314021592982715L;
+ /**
+ * Serializable
+ */
+ private static final long serialVersionUID = -785314021592982715L;
}
diff --git a/src/main/java/org/java_websocket/exceptions/WrappedIOException.java b/src/main/java/org/java_websocket/exceptions/WrappedIOException.java
new file mode 100644
index 000000000..3ec31773d
--- /dev/null
+++ b/src/main/java/org/java_websocket/exceptions/WrappedIOException.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2010-2020 Nathan Rajlich
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+package org.java_websocket.exceptions;
+
+import java.io.IOException;
+import org.java_websocket.WebSocket;
+
+/**
+ * Exception to wrap an IOException and include information about the websocket which had the
+ * exception
+ *
+ * @since 1.4.1
+ */
+public class WrappedIOException extends Exception {
+
+ /**
+ * The websocket where the IOException happened
+ */
+ private final transient WebSocket connection;
+
+ /**
+ * The IOException
+ */
+ private final IOException ioException;
+
+ /**
+ * Wrapp an IOException and include the websocket
+ *
+ * @param connection the websocket where the IOException happened
+ * @param ioException the IOException
+ */
+ public WrappedIOException(WebSocket connection, IOException ioException) {
+ this.connection = connection;
+ this.ioException = ioException;
+ }
+
+ /**
+ * The websocket where the IOException happened
+ *
+ * @return the websocket for the wrapped IOException
+ */
+ public WebSocket getConnection() {
+ return connection;
+ }
+
+ /**
+ * The wrapped IOException
+ *
+ * @return IOException which is wrapped
+ */
+ public IOException getIOException() {
+ return ioException;
+ }
+}
diff --git a/src/main/java/org/java_websocket/exceptions/package-info.java b/src/main/java/org/java_websocket/exceptions/package-info.java
index e73215f03..2972d3c8a 100644
--- a/src/main/java/org/java_websocket/exceptions/package-info.java
+++ b/src/main/java/org/java_websocket/exceptions/package-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -24,6 +24,7 @@
*/
/**
- * This package encapsulates all implementations in relation with the exceptions thrown in this lib.
+ * This package encapsulates all implementations in relation with the exceptions thrown in this
+ * lib.
*/
package org.java_websocket.exceptions;
\ No newline at end of file
diff --git a/src/main/java/org/java_websocket/extensions/CompressionExtension.java b/src/main/java/org/java_websocket/extensions/CompressionExtension.java
index 66a361bdd..408a1588a 100644
--- a/src/main/java/org/java_websocket/extensions/CompressionExtension.java
+++ b/src/main/java/org/java_websocket/extensions/CompressionExtension.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -33,17 +33,23 @@
/**
* Implementation for a compression extension specified by https://tools.ietf.org/html/rfc7692
+ *
* @since 1.3.5
*/
public abstract class CompressionExtension extends DefaultExtension {
- @Override
- public void isFrameValid( Framedata inputFrame ) throws InvalidDataException {
- if(( inputFrame instanceof DataFrame ) && ( inputFrame.isRSV2() || inputFrame.isRSV3() )) {
- throw new InvalidFrameException( "bad rsv RSV1: " + inputFrame.isRSV1() + " RSV2: " + inputFrame.isRSV2() + " RSV3: " + inputFrame.isRSV3() );
- }
- if(( inputFrame instanceof ControlFrame ) && ( inputFrame.isRSV1() || inputFrame.isRSV2() || inputFrame.isRSV3() )) {
- throw new InvalidFrameException( "bad rsv RSV1: " + inputFrame.isRSV1() + " RSV2: " + inputFrame.isRSV2() + " RSV3: " + inputFrame.isRSV3() );
- }
- }
+ @Override
+ public void isFrameValid(Framedata inputFrame) throws InvalidDataException {
+ if ((inputFrame instanceof DataFrame) && (inputFrame.isRSV2() || inputFrame.isRSV3())) {
+ throw new InvalidFrameException(
+ "bad rsv RSV1: " + inputFrame.isRSV1() + " RSV2: " + inputFrame.isRSV2() + " RSV3: "
+ + inputFrame.isRSV3());
+ }
+ if ((inputFrame instanceof ControlFrame) && (inputFrame.isRSV1() || inputFrame.isRSV2()
+ || inputFrame.isRSV3())) {
+ throw new InvalidFrameException(
+ "bad rsv RSV1: " + inputFrame.isRSV1() + " RSV2: " + inputFrame.isRSV2() + " RSV3: "
+ + inputFrame.isRSV3());
+ }
+ }
}
diff --git a/src/main/java/org/java_websocket/extensions/DefaultExtension.java b/src/main/java/org/java_websocket/extensions/DefaultExtension.java
index 0e9893de9..3892990c1 100644
--- a/src/main/java/org/java_websocket/extensions/DefaultExtension.java
+++ b/src/main/java/org/java_websocket/extensions/DefaultExtension.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -31,73 +31,73 @@
/**
* Class which represents the normal websocket implementation specified by rfc6455.
- *
+ *
* This is a fallback and will always be available for a Draft_6455
*
* @since 1.3.5
*/
public class DefaultExtension implements IExtension {
- @Override
- public void decodeFrame( Framedata inputFrame ) throws InvalidDataException {
- //Nothing to do here
- }
+ @Override
+ public void decodeFrame(Framedata inputFrame) throws InvalidDataException {
+ //Nothing to do here
+ }
- @Override
- public void encodeFrame( Framedata inputFrame ) {
- //Nothing to do here
- }
+ @Override
+ public void encodeFrame(Framedata inputFrame) {
+ //Nothing to do here
+ }
- @Override
- public boolean acceptProvidedExtensionAsServer( String inputExtension ) {
- return true;
- }
+ @Override
+ public boolean acceptProvidedExtensionAsServer(String inputExtension) {
+ return true;
+ }
- @Override
- public boolean acceptProvidedExtensionAsClient( String inputExtension ) {
- return true;
- }
+ @Override
+ public boolean acceptProvidedExtensionAsClient(String inputExtension) {
+ return true;
+ }
- @Override
- public void isFrameValid( Framedata inputFrame ) throws InvalidDataException {
- if( inputFrame.isRSV1() || inputFrame.isRSV2() || inputFrame.isRSV3() ) {
- throw new InvalidFrameException( "bad rsv RSV1: " + inputFrame.isRSV1() + " RSV2: " + inputFrame.isRSV2() + " RSV3: " + inputFrame.isRSV3() );
- }
- }
+ @Override
+ public void isFrameValid(Framedata inputFrame) throws InvalidDataException {
+ if (inputFrame.isRSV1() || inputFrame.isRSV2() || inputFrame.isRSV3()) {
+ throw new InvalidFrameException(
+ "bad rsv RSV1: " + inputFrame.isRSV1() + " RSV2: " + inputFrame.isRSV2() + " RSV3: "
+ + inputFrame.isRSV3());
+ }
+ }
- @Override
- public String getProvidedExtensionAsClient() {
- return "";
- }
+ @Override
+ public String getProvidedExtensionAsClient() {
+ return "";
+ }
- @Override
- public String getProvidedExtensionAsServer() {
- return "";
- }
+ @Override
+ public String getProvidedExtensionAsServer() {
+ return "";
+ }
- @Override
- public IExtension copyInstance() {
- return new DefaultExtension();
- }
+ @Override
+ public IExtension copyInstance() {
+ return new DefaultExtension();
+ }
- public void reset() {
- //Nothing to do here. No internal stats.
- }
+ public void reset() {
+ //Nothing to do here. No internal stats.
+ }
- @Override
- public String toString() {
- return getClass().getSimpleName();
- }
+ @Override
+ public String toString() {
+ return getClass().getSimpleName();
+ }
- @Override
- public int hashCode() {
- return getClass().hashCode();
- }
+ @Override
+ public int hashCode() {
+ return getClass().hashCode();
+ }
- @Override
- public boolean equals( Object o ) {
- if( this == o ) return true;
- if( o == null ) return false;
- return getClass() == o.getClass();
- }
+ @Override
+ public boolean equals(Object o) {
+ return this == o || o != null && getClass() == o.getClass();
+ }
}
diff --git a/src/main/java/org/java_websocket/extensions/ExtensionRequestData.java b/src/main/java/org/java_websocket/extensions/ExtensionRequestData.java
new file mode 100644
index 000000000..37bc9de20
--- /dev/null
+++ b/src/main/java/org/java_websocket/extensions/ExtensionRequestData.java
@@ -0,0 +1,53 @@
+package org.java_websocket.extensions;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class ExtensionRequestData {
+
+ public static final String EMPTY_VALUE = "";
+
+ private Map extensionParameters;
+ private String extensionName;
+
+ private ExtensionRequestData() {
+ extensionParameters = new LinkedHashMap<>();
+ }
+
+ public static ExtensionRequestData parseExtensionRequest(String extensionRequest) {
+ ExtensionRequestData extensionData = new ExtensionRequestData();
+ String[] parts = extensionRequest.split(";");
+ extensionData.extensionName = parts[0].trim();
+
+ for (int i = 1; i < parts.length; i++) {
+ String[] keyValue = parts[i].split("=");
+ String value = EMPTY_VALUE;
+
+ // Some parameters don't take a value. For those that do, parse the value.
+ if (keyValue.length > 1) {
+ String tempValue = keyValue[1].trim();
+
+ // If the value is wrapped in quotes, just get the data between them.
+ if ((tempValue.startsWith("\"") && tempValue.endsWith("\""))
+ || (tempValue.startsWith("'") && tempValue.endsWith("'"))
+ && tempValue.length() > 2) {
+ tempValue = tempValue.substring(1, tempValue.length() - 1);
+ }
+
+ value = tempValue;
+ }
+
+ extensionData.extensionParameters.put(keyValue[0].trim(), value);
+ }
+
+ return extensionData;
+ }
+
+ public String getExtensionName() {
+ return extensionName;
+ }
+
+ public Map getExtensionParameters() {
+ return extensionParameters;
+ }
+}
diff --git a/src/main/java/org/java_websocket/extensions/IExtension.java b/src/main/java/org/java_websocket/extensions/IExtension.java
index af5c65c27..02bf581a4 100644
--- a/src/main/java/org/java_websocket/extensions/IExtension.java
+++ b/src/main/java/org/java_websocket/extensions/IExtension.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -30,95 +30,109 @@
/**
* Interface which specifies all required methods to develop a websocket extension.
+ *
* @since 1.3.5
*/
public interface IExtension {
- /**
- * Decode a frame with a extension specific algorithm.
- * The algorithm is subject to be implemented by the specific extension.
- * The resulting frame will be used in the application
- *
- * @param inputFrame the frame, which has do be decoded to be used in the application
- * @throws InvalidDataException Throw InvalidDataException if the received frame is not correctly implemented by the other endpoint or there are other protocol errors/decoding errors
- * @since 1.3.5
- */
- void decodeFrame( Framedata inputFrame ) throws InvalidDataException;
+ /**
+ * Decode a frame with a extension specific algorithm. The algorithm is subject to be implemented
+ * by the specific extension. The resulting frame will be used in the application
+ *
+ * @param inputFrame the frame, which has do be decoded to be used in the application
+ * @throws InvalidDataException Throw InvalidDataException if the received frame is not correctly
+ * implemented by the other endpoint or there are other protocol
+ * errors/decoding errors
+ * @since 1.3.5
+ */
+ void decodeFrame(Framedata inputFrame) throws InvalidDataException;
- /**
- * Encode a frame with a extension specific algorithm.
- * The algorithm is subject to be implemented by the specific extension.
- * The resulting frame will be send to the other endpoint.
- *
- * @param inputFrame the frame, which has do be encoded to be used on the other endpoint
- * @since 1.3.5
- */
- void encodeFrame( Framedata inputFrame );
+ /**
+ * Encode a frame with a extension specific algorithm. The algorithm is subject to be implemented
+ * by the specific extension. The resulting frame will be send to the other endpoint.
+ *
+ * @param inputFrame the frame, which has do be encoded to be used on the other endpoint
+ * @since 1.3.5
+ */
+ void encodeFrame(Framedata inputFrame);
- /**
- * Check if the received Sec-WebSocket-Extensions header field contains a offer for the specific extension if the endpoint is in the role of a server
- *
- * @param inputExtensionHeader the received Sec-WebSocket-Extensions header field offered by the other endpoint
- * @return true, if the offer does fit to this specific extension
- * @since 1.3.5
- */
- boolean acceptProvidedExtensionAsServer( String inputExtensionHeader );
+ /**
+ * Check if the received Sec-WebSocket-Extensions header field contains a offer for the specific
+ * extension if the endpoint is in the role of a server
+ *
+ * @param inputExtensionHeader the received Sec-WebSocket-Extensions header field offered by the
+ * other endpoint
+ * @return true, if the offer does fit to this specific extension
+ * @since 1.3.5
+ */
+ boolean acceptProvidedExtensionAsServer(String inputExtensionHeader);
- /**
- * Check if the received Sec-WebSocket-Extensions header field contains a offer for the specific extension if the endpoint is in the role of a client
- *
- * @param inputExtensionHeader the received Sec-WebSocket-Extensions header field offered by the other endpoint
- * @return true, if the offer does fit to this specific extension
- * @since 1.3.5
- */
- boolean acceptProvidedExtensionAsClient( String inputExtensionHeader );
+ /**
+ * Check if the received Sec-WebSocket-Extensions header field contains a offer for the specific
+ * extension if the endpoint is in the role of a client
+ *
+ * @param inputExtensionHeader the received Sec-WebSocket-Extensions header field offered by the
+ * other endpoint
+ * @return true, if the offer does fit to this specific extension
+ * @since 1.3.5
+ */
+ boolean acceptProvidedExtensionAsClient(String inputExtensionHeader);
- /**
- * Check if the received frame is correctly implemented by the other endpoint and there are no specification errors (like wrongly set RSV)
- *
- * @param inputFrame the received frame
- * @throws InvalidDataException Throw InvalidDataException if the received frame is not correctly implementing the specification for the specific extension
- * @since 1.3.5
- */
- void isFrameValid( Framedata inputFrame ) throws InvalidDataException;
+ /**
+ * Check if the received frame is correctly implemented by the other endpoint and there are no
+ * specification errors (like wrongly set RSV)
+ *
+ * @param inputFrame the received frame
+ * @throws InvalidDataException Throw InvalidDataException if the received frame is not correctly
+ * implementing the specification for the specific extension
+ * @since 1.3.5
+ */
+ void isFrameValid(Framedata inputFrame) throws InvalidDataException;
- /**
- * Return the specific Sec-WebSocket-Extensions header offer for this extension if the endpoint is in the role of a client.
- * If the extension returns an empty string (""), the offer will not be included in the handshake.
- *
- * @return the specific Sec-WebSocket-Extensions header for this extension
- * @since 1.3.5
- */
- String getProvidedExtensionAsClient();
+ /**
+ * Return the specific Sec-WebSocket-Extensions header offer for this extension if the endpoint is
+ * in the role of a client. If the extension returns an empty string (""), the offer will not be
+ * included in the handshake.
+ *
+ * @return the specific Sec-WebSocket-Extensions header for this extension
+ * @since 1.3.5
+ */
+ String getProvidedExtensionAsClient();
- /**
- * Return the specific Sec-WebSocket-Extensions header offer for this extension if the endpoint is in the role of a server.
- * If the extension returns an empty string (""), the offer will not be included in the handshake.
- *
- * @return the specific Sec-WebSocket-Extensions header for this extension
- * @since 1.3.5
- */
- String getProvidedExtensionAsServer();
+ /**
+ * Return the specific Sec-WebSocket-Extensions header offer for this extension if the endpoint is
+ * in the role of a server. If the extension returns an empty string (""), the offer will not be
+ * included in the handshake.
+ *
+ * @return the specific Sec-WebSocket-Extensions header for this extension
+ * @since 1.3.5
+ */
+ String getProvidedExtensionAsServer();
- /**
- * Extensions must only be by one websocket at all. To prevent extensions to be used more than once the Websocket implementation should call this method in order to create a new usable version of a given extension instance.
- * The copy can be safely used in conjunction with a new websocket connection.
- * @return a copy of the extension
- * @since 1.3.5
- */
- IExtension copyInstance();
+ /**
+ * Extensions must only be by one websocket at all. To prevent extensions to be used more than
+ * once the Websocket implementation should call this method in order to create a new usable
+ * version of a given extension instance.
The copy can be safely used in conjunction with a
+ * new websocket connection.
+ *
+ * @return a copy of the extension
+ * @since 1.3.5
+ */
+ IExtension copyInstance();
- /**
- * Cleaning up internal stats when the draft gets reset.
- * @since 1.3.5
- */
- void reset();
+ /**
+ * Cleaning up internal stats when the draft gets reset.
+ *
+ * @since 1.3.5
+ */
+ void reset();
- /**
- * Return a string which should contain the class name as well as additional information about the current configurations for this extension (DEBUG purposes)
- *
- * @return a string containing the class name as well as additional information
- * @since 1.3.5
- */
- String toString();
+ /**
+ * Return a string which should contain the class name as well as additional information about the
+ * current configurations for this extension (DEBUG purposes)
+ *
+ * @return a string containing the class name as well as additional information
+ * @since 1.3.5
+ */
+ String toString();
}
diff --git a/src/main/java/org/java_websocket/extensions/package-info.java b/src/main/java/org/java_websocket/extensions/package-info.java
index 639ef595e..251cbdf9f 100644
--- a/src/main/java/org/java_websocket/extensions/package-info.java
+++ b/src/main/java/org/java_websocket/extensions/package-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -24,6 +24,7 @@
*/
/**
- * This package encapsulates all interfaces and implementations in relation with the WebSocket Sec-WebSocket-Extensions.
+ * This package encapsulates all interfaces and implementations in relation with the WebSocket
+ * Sec-WebSocket-Extensions.
*/
package org.java_websocket.extensions;
\ No newline at end of file
diff --git a/src/main/java/org/java_websocket/extensions/permessage_deflate/PerMessageDeflateExtension.java b/src/main/java/org/java_websocket/extensions/permessage_deflate/PerMessageDeflateExtension.java
new file mode 100644
index 000000000..9eb16ca15
--- /dev/null
+++ b/src/main/java/org/java_websocket/extensions/permessage_deflate/PerMessageDeflateExtension.java
@@ -0,0 +1,372 @@
+package org.java_websocket.extensions.permessage_deflate;
+
+import java.io.ByteArrayOutputStream;
+import java.nio.ByteBuffer;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.zip.DataFormatException;
+import java.util.zip.Deflater;
+import java.util.zip.Inflater;
+import org.java_websocket.enums.Opcode;
+import org.java_websocket.exceptions.InvalidDataException;
+import org.java_websocket.exceptions.InvalidFrameException;
+import org.java_websocket.extensions.CompressionExtension;
+import org.java_websocket.extensions.ExtensionRequestData;
+import org.java_websocket.extensions.IExtension;
+import org.java_websocket.framing.CloseFrame;
+import org.java_websocket.framing.ContinuousFrame;
+import org.java_websocket.framing.DataFrame;
+import org.java_websocket.framing.Framedata;
+import org.java_websocket.framing.FramedataImpl1;
+
+/**
+ * PerMessage Deflate Extension (7. The
+ * "permessage-deflate" Extension in
+ * RFC 7692).
+ *
+ * @see 7. The "permessage-deflate"
+ * Extension in RFC 7692
+ */
+public class PerMessageDeflateExtension extends CompressionExtension {
+
+ // Name of the extension as registered by IETF https://tools.ietf.org/html/rfc7692#section-9.
+ private static final String EXTENSION_REGISTERED_NAME = "permessage-deflate";
+ // Below values are defined for convenience. They are not used in the compression/decompression phase.
+ // They may be needed during the extension-negotiation offer in the future.
+ private static final String SERVER_NO_CONTEXT_TAKEOVER = "server_no_context_takeover";
+ private static final String CLIENT_NO_CONTEXT_TAKEOVER = "client_no_context_takeover";
+ private static final String SERVER_MAX_WINDOW_BITS = "server_max_window_bits";
+ private static final String CLIENT_MAX_WINDOW_BITS = "client_max_window_bits";
+ private static final int serverMaxWindowBits = 1 << 15;
+ private static final int clientMaxWindowBits = 1 << 15;
+ private static final byte[] TAIL_BYTES = {(byte) 0x00, (byte) 0x00, (byte) 0xFF, (byte) 0xFF};
+ private static final int BUFFER_SIZE = 1 << 10;
+
+ private int threshold = 1024;
+
+ private boolean serverNoContextTakeover = true;
+ private boolean clientNoContextTakeover = false;
+
+ // For WebSocketServers, this variable holds the extension parameters that the peer client has requested.
+ // For WebSocketClients, this variable holds the extension parameters that client himself has requested.
+ private Map requestedParameters = new LinkedHashMap<>();
+
+ private final int compressionLevel;
+
+ private final Inflater inflater;
+ private final Deflater deflater;
+
+ /**
+ * Constructor for the PerMessage Deflate Extension (7. Thepermessage-deflate" Extension)
+ *
+ * Uses {@link java.util.zip.Deflater#DEFAULT_COMPRESSION} as the compression level for the {@link java.util.zip.Deflater#Deflater(int)}
+ */
+ public PerMessageDeflateExtension() {
+ this(Deflater.DEFAULT_COMPRESSION);
+ }
+
+ /**
+ * Constructor for the PerMessage Deflate Extension (7. Thepermessage-deflate" Extension)
+ *
+ * @param compressionLevel The compression level passed to the {@link java.util.zip.Deflater#Deflater(int)}
+ */
+ public PerMessageDeflateExtension(int compressionLevel) {
+ this.compressionLevel = compressionLevel;
+ this.deflater = new Deflater(this.compressionLevel, true);
+ this.inflater = new Inflater(true);
+ }
+
+ /**
+ * Get the compression level used for the compressor.
+ * @return the compression level
+ */
+ public int getCompressionLevel() {
+ return this.compressionLevel;
+ }
+
+ /**
+ * Get the size threshold for doing the compression
+ * @return Size (in bytes) below which messages will not be compressed
+ * @since 1.5.3
+ */
+ public int getThreshold() {
+ return threshold;
+ }
+
+ /**
+ * Set the size when payloads smaller than this will not be compressed.
+ * @param threshold the size in bytes
+ * @since 1.5.3
+ */
+ public void setThreshold(int threshold) {
+ this.threshold = threshold;
+ }
+
+ /**
+ * Access the "server_no_context_takeover" extension parameter
+ *
+ * @see The "server_no_context_takeover" Extension Parameter
+ * @return serverNoContextTakeover is the server no context parameter active
+ */
+ public boolean isServerNoContextTakeover() {
+ return serverNoContextTakeover;
+ }
+
+ /**
+ * Setter for the "server_no_context_takeover" extension parameter
+ * @see The "server_no_context_takeover" Extension Parameter
+ * @param serverNoContextTakeover set the server no context parameter
+ */
+ public void setServerNoContextTakeover(boolean serverNoContextTakeover) {
+ this.serverNoContextTakeover = serverNoContextTakeover;
+ }
+
+ /**
+ * Access the "client_no_context_takeover" extension parameter
+ *
+ * @see The "client_no_context_takeover" Extension Parameter
+ * @return clientNoContextTakeover is the client no context parameter active
+ */
+ public boolean isClientNoContextTakeover() {
+ return clientNoContextTakeover;
+ }
+
+ /**
+ * Setter for the "client_no_context_takeover" extension parameter
+ * @see The "client_no_context_takeover" Extension Parameter
+ * @param clientNoContextTakeover set the client no context parameter
+ */
+ public void setClientNoContextTakeover(boolean clientNoContextTakeover) {
+ this.clientNoContextTakeover = clientNoContextTakeover;
+ }
+
+ /*
+ An endpoint uses the following algorithm to decompress a message.
+ 1. Append 4 octets of 0x00 0x00 0xff 0xff to the tail end of the
+ payload of the message.
+ 2. Decompress the resulting data using DEFLATE.
+ See, https://tools.ietf.org/html/rfc7692#section-7.2.2
+ */
+ @Override
+ public void decodeFrame(Framedata inputFrame) throws InvalidDataException {
+ // Only DataFrames can be decompressed.
+ if (!(inputFrame instanceof DataFrame)) {
+ return;
+ }
+
+ if (!inputFrame.isRSV1() && inputFrame.getOpcode() != Opcode.CONTINUOUS) {
+ return;
+ }
+
+ // RSV1 bit must be set only for the first frame.
+ if (inputFrame.getOpcode() == Opcode.CONTINUOUS && inputFrame.isRSV1()) {
+ throw new InvalidDataException(CloseFrame.POLICY_VALIDATION,
+ "RSV1 bit can only be set for the first frame.");
+ }
+
+ // Decompressed output buffer.
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ try {
+ decompress(inputFrame.getPayloadData().array(), output);
+
+ /*
+ If a message is "first fragmented and then compressed", as this project does, then the inflater
+ can not inflate fragments except the first one.
+ This behavior occurs most likely because those fragments end with "final deflate blocks".
+ We can check the getRemaining() method to see whether the data we supplied has been decompressed or not.
+ And if not, we just reset the inflater and decompress again.
+ Note that this behavior doesn't occur if the message is "first compressed and then fragmented".
+ */
+ if (inflater.getRemaining() > 0) {
+ inflater.reset();
+ decompress(inputFrame.getPayloadData().array(), output);
+ }
+
+ if (inputFrame.isFin()) {
+ decompress(TAIL_BYTES, output);
+ // If context takeover is disabled, inflater can be reset.
+ if (clientNoContextTakeover) {
+ inflater.reset();
+ }
+ }
+ } catch (DataFormatException e) {
+ throw new InvalidDataException(CloseFrame.POLICY_VALIDATION, e.getMessage());
+ }
+
+ // Set frames payload to the new decompressed data.
+ ((FramedataImpl1) inputFrame)
+ .setPayload(ByteBuffer.wrap(output.toByteArray(), 0, output.size()));
+ }
+
+ /**
+ * @param data the bytes of data
+ * @param outputBuffer the output stream
+ * @throws DataFormatException
+ */
+ private void decompress(byte[] data, ByteArrayOutputStream outputBuffer)
+ throws DataFormatException {
+ inflater.setInput(data);
+ byte[] buffer = new byte[BUFFER_SIZE];
+
+ int bytesInflated;
+ while ((bytesInflated = inflater.inflate(buffer)) > 0) {
+ outputBuffer.write(buffer, 0, bytesInflated);
+ }
+ }
+
+ @Override
+ public void encodeFrame(Framedata inputFrame) {
+ // Only DataFrames can be decompressed.
+ if (!(inputFrame instanceof DataFrame)) {
+ return;
+ }
+
+ byte[] payloadData = inputFrame.getPayloadData().array();
+ if (payloadData.length < threshold) {
+ return;
+ }
+ // Only the first frame's RSV1 must be set.
+ if (!(inputFrame instanceof ContinuousFrame)) {
+ ((DataFrame) inputFrame).setRSV1(true);
+ }
+
+ deflater.setInput(payloadData);
+ // Compressed output buffer.
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ // Temporary buffer to hold compressed output.
+ byte[] buffer = new byte[1024];
+ int bytesCompressed;
+ while ((bytesCompressed = deflater.deflate(buffer, 0, buffer.length, Deflater.SYNC_FLUSH))
+ > 0) {
+ output.write(buffer, 0, bytesCompressed);
+ }
+
+ byte[] outputBytes = output.toByteArray();
+ int outputLength = outputBytes.length;
+
+ /*
+ https://tools.ietf.org/html/rfc7692#section-7.2.1 states that if the final fragment's compressed
+ payload ends with 0x00 0x00 0xff 0xff, they should be removed.
+ To simulate removal, we just pass 4 bytes less to the new payload
+ if the frame is final and outputBytes ends with 0x00 0x00 0xff 0xff.
+ */
+ if (inputFrame.isFin()) {
+ if (endsWithTail(outputBytes)) {
+ outputLength -= TAIL_BYTES.length;
+ }
+
+ if (serverNoContextTakeover) {
+ deflater.reset();
+ }
+ }
+
+ // Set frames payload to the new compressed data.
+ ((FramedataImpl1) inputFrame).setPayload(ByteBuffer.wrap(outputBytes, 0, outputLength));
+ }
+
+ /**
+ * @param data the bytes of data
+ * @return true if the data is OK
+ */
+ private static boolean endsWithTail(byte[] data) {
+ if (data.length < 4) {
+ return false;
+ }
+
+ int length = data.length;
+ for (int i = 0; i < TAIL_BYTES.length; i++) {
+ if (TAIL_BYTES[i] != data[length - TAIL_BYTES.length + i]) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean acceptProvidedExtensionAsServer(String inputExtension) {
+ String[] requestedExtensions = inputExtension.split(",");
+ for (String extension : requestedExtensions) {
+ ExtensionRequestData extensionData = ExtensionRequestData.parseExtensionRequest(extension);
+ if (!EXTENSION_REGISTERED_NAME.equalsIgnoreCase(extensionData.getExtensionName())) {
+ continue;
+ }
+
+ // Holds parameters that peer client has sent.
+ Map headers = extensionData.getExtensionParameters();
+ requestedParameters.putAll(headers);
+ if (requestedParameters.containsKey(CLIENT_NO_CONTEXT_TAKEOVER)) {
+ clientNoContextTakeover = true;
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean acceptProvidedExtensionAsClient(String inputExtension) {
+ String[] requestedExtensions = inputExtension.split(",");
+ for (String extension : requestedExtensions) {
+ ExtensionRequestData extensionData = ExtensionRequestData.parseExtensionRequest(extension);
+ if (!EXTENSION_REGISTERED_NAME.equalsIgnoreCase(extensionData.getExtensionName())) {
+ continue;
+ }
+
+ // Holds parameters that are sent by the server, as a response to our initial extension request.
+ Map headers = extensionData.getExtensionParameters();
+ // After this point, parameters that the server sent back can be configured, but we don't use them for now.
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public String getProvidedExtensionAsClient() {
+ requestedParameters.put(CLIENT_NO_CONTEXT_TAKEOVER, ExtensionRequestData.EMPTY_VALUE);
+ requestedParameters.put(SERVER_NO_CONTEXT_TAKEOVER, ExtensionRequestData.EMPTY_VALUE);
+
+ return EXTENSION_REGISTERED_NAME + "; " + SERVER_NO_CONTEXT_TAKEOVER + "; "
+ + CLIENT_NO_CONTEXT_TAKEOVER;
+ }
+
+ @Override
+ public String getProvidedExtensionAsServer() {
+ return EXTENSION_REGISTERED_NAME
+ + "; " + SERVER_NO_CONTEXT_TAKEOVER
+ + (clientNoContextTakeover ? "; " + CLIENT_NO_CONTEXT_TAKEOVER : "");
+ }
+
+ @Override
+ public IExtension copyInstance() {
+ PerMessageDeflateExtension clone = new PerMessageDeflateExtension(this.getCompressionLevel());
+ clone.setThreshold(this.getThreshold());
+ clone.setClientNoContextTakeover(this.isClientNoContextTakeover());
+ clone.setServerNoContextTakeover(this.isServerNoContextTakeover());
+ return clone;
+ }
+
+ /**
+ * This extension requires the RSV1 bit to be set only for the first frame. If the frame is type
+ * is CONTINUOUS, RSV1 bit must be unset.
+ */
+ @Override
+ public void isFrameValid(Framedata inputFrame) throws InvalidDataException {
+ if ((inputFrame instanceof ContinuousFrame) && (inputFrame.isRSV1() || inputFrame.isRSV2()
+ || inputFrame.isRSV3())) {
+ throw new InvalidFrameException(
+ "bad rsv RSV1: " + inputFrame.isRSV1() + " RSV2: " + inputFrame.isRSV2() + " RSV3: "
+ + inputFrame.isRSV3());
+ }
+ super.isFrameValid(inputFrame);
+ }
+
+ @Override
+ public String toString() {
+ return "PerMessageDeflateExtension";
+ }
+
+
+}
diff --git a/src/main/java/org/java_websocket/framing/BinaryFrame.java b/src/main/java/org/java_websocket/framing/BinaryFrame.java
index e20568612..dc0544954 100644
--- a/src/main/java/org/java_websocket/framing/BinaryFrame.java
+++ b/src/main/java/org/java_websocket/framing/BinaryFrame.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -25,15 +25,17 @@
package org.java_websocket.framing;
+import org.java_websocket.enums.Opcode;
+
/**
* Class to represent a binary frame
*/
public class BinaryFrame extends DataFrame {
- /**
- * constructor which sets the opcode of this frame to binary
- */
- public BinaryFrame() {
- super(Opcode.BINARY);
- }
+ /**
+ * constructor which sets the opcode of this frame to binary
+ */
+ public BinaryFrame() {
+ super(Opcode.BINARY);
+ }
}
diff --git a/src/main/java/org/java_websocket/framing/CloseFrame.java b/src/main/java/org/java_websocket/framing/CloseFrame.java
index 8fbadfe1e..5a63d8ae5 100644
--- a/src/main/java/org/java_websocket/framing/CloseFrame.java
+++ b/src/main/java/org/java_websocket/framing/CloseFrame.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -25,285 +25,317 @@
package org.java_websocket.framing;
+import java.nio.ByteBuffer;
+import org.java_websocket.enums.Opcode;
import org.java_websocket.exceptions.InvalidDataException;
import org.java_websocket.exceptions.InvalidFrameException;
import org.java_websocket.util.ByteBufferUtils;
import org.java_websocket.util.Charsetfunctions;
-import java.nio.ByteBuffer;
-
/**
* Class to represent a close frame
*/
public class CloseFrame extends ControlFrame {
- /**
- * indicates a normal closure, meaning whatever purpose the
- * connection was established for has been fulfilled.
- */
- public static final int NORMAL = 1000;
- /**
- * 1001 indicates that an endpoint is "going away", such as a server
- * going down, or a browser having navigated away from a page.
- */
- public static final int GOING_AWAY = 1001;
- /**
- * 1002 indicates that an endpoint is terminating the connection due
- * to a protocol error.
- */
- public static final int PROTOCOL_ERROR = 1002;
- /**
- * 1003 indicates that an endpoint is terminating the connection
- * because it has received a type of data it cannot accept (e.g. an
- * endpoint that understands only text data MAY send this if it
- * receives a binary message).
- */
- public static final int REFUSE = 1003;
- /*1004: Reserved. The specific meaning might be defined in the future.*/
- /**
- * 1005 is a reserved value and MUST NOT be set as a status code in a
- * Close control frame by an endpoint. It is designated for use in
- * applications expecting a status code to indicate that no status
- * code was actually present.
- */
- public static final int NOCODE = 1005;
- /**
- * 1006 is a reserved value and MUST NOT be set as a status code in a
- * Close control frame by an endpoint. It is designated for use in
- * applications expecting a status code to indicate that the
- * connection was closed abnormally, e.g. without sending or
- * receiving a Close control frame.
- */
- public static final int ABNORMAL_CLOSE = 1006;
- /**
- * 1007 indicates that an endpoint is terminating the connection
- * because it has received data within a message that was not
- * consistent with the type of the message (e.g., non-UTF-8 [RFC3629]
- * data within a text message).
- */
- public static final int NO_UTF8 = 1007;
- /**
- * 1008 indicates that an endpoint is terminating the connection
- * because it has received a message that violates its policy. This
- * is a generic status code that can be returned when there is no
- * other more suitable status code (e.g. 1003 or 1009), or if there
- * is a need to hide specific details about the policy.
- */
- public static final int POLICY_VALIDATION = 1008;
- /**
- * 1009 indicates that an endpoint is terminating the connection
- * because it has received a message which is too big for it to
- * process.
- */
- public static final int TOOBIG = 1009;
- /**
- * 1010 indicates that an endpoint (client) is terminating the
- * connection because it has expected the server to negotiate one or
- * more extension, but the server didn't return them in the response
- * message of the WebSocket handshake. The list of extensions which
- * are needed SHOULD appear in the /reason/ part of the Close frame.
- * Note that this status code is not used by the server, because it
- * can fail the WebSocket handshake instead.
- */
- public static final int EXTENSION = 1010;
- /**
- * 1011 indicates that a server is terminating the connection because
- * it encountered an unexpected condition that prevented it from
- * fulfilling the request.
- **/
- public static final int UNEXPECTED_CONDITION = 1011;
- /**
- * 1012 indicates that the service is restarted.
- * A client may reconnect, and if it choses to do, should reconnect using a randomized delay of 5 - 30s.
- * See https://www.ietf.org/mail-archive/web/hybi/current/msg09670.html for more information.
- *
- * @since 1.3.8
- **/
- public static final int SERVICE_RESTART = 1012;
- /**
- * 1013 indicates that the service is experiencing overload.
- * A client should only connect to a different IP (when there are multiple for the target)
- * or reconnect to the same IP upon user action.
- * See https://www.ietf.org/mail-archive/web/hybi/current/msg09670.html for more information.
- *
- * @since 1.3.8
- **/
- public static final int TRY_AGAIN_LATER = 1013;
- /**
- * 1014 indicates that the server was acting as a gateway or proxy and received an
- * invalid response from the upstream server. This is similar to 502 HTTP Status Code
- * See https://www.ietf.org/mail-archive/web/hybi/current/msg10748.html fore more information.
- *
- * @since 1.3.8
- **/
- public static final int BAD_GATEWAY = 1014;
- /**
- * 1015 is a reserved value and MUST NOT be set as a status code in a
- * Close control frame by an endpoint. It is designated for use in
- * applications expecting a status code to indicate that the
- * connection was closed due to a failure to perform a TLS handshake
- * (e.g., the server certificate can't be verified).
- **/
- public static final int TLS_ERROR = 1015;
+ /**
+ * indicates a normal closure, meaning whatever purpose the connection was established for has
+ * been fulfilled.
+ */
+ public static final int NORMAL = 1000;
+ /**
+ * 1001 indicates that an endpoint is "going away", such as a server going down, or a browser
+ * having navigated away from a page.
+ */
+ public static final int GOING_AWAY = 1001;
+ /**
+ * 1002 indicates that an endpoint is terminating the connection due to a protocol error.
+ */
+ public static final int PROTOCOL_ERROR = 1002;
+ /**
+ * 1003 indicates that an endpoint is terminating the connection because it has received a type of
+ * data it cannot accept (e.g. an endpoint that understands only text data MAY send this if it
+ * receives a binary message).
+ */
+ public static final int REFUSE = 1003;
+ /*1004: Reserved. The specific meaning might be defined in the future.*/
+ /**
+ * 1005 is a reserved value and MUST NOT be set as a status code in a Close control frame by an
+ * endpoint. It is designated for use in applications expecting a status code to indicate that no
+ * status code was actually present.
+ */
+ public static final int NOCODE = 1005;
+ /**
+ * 1006 is a reserved value and MUST NOT be set as a status code in a Close control frame by an
+ * endpoint. It is designated for use in applications expecting a status code to indicate that the
+ * connection was closed abnormally, e.g. without sending or receiving a Close control frame.
+ */
+ public static final int ABNORMAL_CLOSE = 1006;
+ /**
+ * 1007 indicates that an endpoint is terminating the connection because it has received data
+ * within a message that was not consistent with the type of the message (e.g., non-UTF-8
+ * [RFC3629] data within a text message).
+ */
+ public static final int NO_UTF8 = 1007;
+ /**
+ * 1008 indicates that an endpoint is terminating the connection because it has received a message
+ * that violates its policy. This is a generic status code that can be returned when there is no
+ * other more suitable status code (e.g. 1003 or 1009), or if there is a need to hide specific
+ * details about the policy.
+ */
+ public static final int POLICY_VALIDATION = 1008;
+ /**
+ * 1009 indicates that an endpoint is terminating the connection because it has received a message
+ * which is too big for it to process.
+ */
+ public static final int TOOBIG = 1009;
+ /**
+ * 1010 indicates that an endpoint (client) is terminating the connection because it has expected
+ * the server to negotiate one or more extension, but the server didn't return them in the
+ * response message of the WebSocket handshake. The list of extensions which are needed SHOULD
+ * appear in the /reason/ part of the Close frame. Note that this status code is not used by the
+ * server, because it can fail the WebSocket handshake instead.
+ */
+ public static final int EXTENSION = 1010;
+ /**
+ * 1011 indicates that a server is terminating the connection because it encountered an unexpected
+ * condition that prevented it from fulfilling the request.
+ **/
+ public static final int UNEXPECTED_CONDITION = 1011;
+ /**
+ * 1012 indicates that the service is restarted. A client may reconnect, and if it choses to do,
+ * should reconnect using a randomized delay of 5 - 30s. See https://www.ietf.org/mail-archive/web/hybi/current/msg09670.html
+ * for more information.
+ *
+ * @since 1.3.8
+ **/
+ public static final int SERVICE_RESTART = 1012;
+ /**
+ * 1013 indicates that the service is experiencing overload. A client should only connect to a
+ * different IP (when there are multiple for the target) or reconnect to the same IP upon user
+ * action. See https://www.ietf.org/mail-archive/web/hybi/current/msg09670.html for more
+ * information.
+ *
+ * @since 1.3.8
+ **/
+ public static final int TRY_AGAIN_LATER = 1013;
+ /**
+ * 1014 indicates that the server was acting as a gateway or proxy and received an invalid
+ * response from the upstream server. This is similar to 502 HTTP Status Code See
+ * https://www.ietf.org/mail-archive/web/hybi/current/msg10748.html fore more information.
+ *
+ * @since 1.3.8
+ **/
+ public static final int BAD_GATEWAY = 1014;
+ /**
+ * 1015 is a reserved value and MUST NOT be set as a status code in a Close control frame by an
+ * endpoint. It is designated for use in applications expecting a status code to indicate that the
+ * connection was closed due to a failure to perform a TLS handshake (e.g., the server certificate
+ * can't be verified).
+ **/
+ public static final int TLS_ERROR = 1015;
+
+ /**
+ * The connection had never been established
+ */
+ public static final int NEVER_CONNECTED = -1;
- /**
- * The connection had never been established
- */
- public static final int NEVER_CONNECTED = -1;
+ /**
+ * The connection had a buggy close (this should not happen)
+ */
+ public static final int BUGGYCLOSE = -2;
- /**
- * The connection had a buggy close (this should not happen)
- */
- public static final int BUGGYCLOSE = -2;
+ /**
+ * The connection was flushed and closed
+ */
+ public static final int FLASHPOLICY = -3;
- /**
- * The connection was flushed and closed
- */
- public static final int FLASHPOLICY = -3;
+ /**
+ * The close code used in this close frame
+ */
+ private int code;
- /**
- * The close code used in this close frame
- */
- private int code;
+ /**
+ * The close message used in this close frame
+ */
+ private String reason;
- /**
- * The close message used in this close frame
- */
- private String reason;
+ /**
+ * Constructor for a close frame
+ *
+ * Using opcode closing and fin = true
+ */
+ public CloseFrame() {
+ super(Opcode.CLOSING);
+ setReason("");
+ setCode(CloseFrame.NORMAL);
+ }
- /**
- * Constructor for a close frame
- *
- * Using opcode closing and fin = true
- */
- public CloseFrame() {
- super(Opcode.CLOSING);
- setReason("");
- setCode(CloseFrame.NORMAL);
+ /**
+ * Set the close code for this close frame
+ *
+ * @param code the close code
+ */
+ public void setCode(int code) {
+ this.code = code;
+ // CloseFrame.TLS_ERROR is not allowed to be transferred over the wire
+ if (code == CloseFrame.TLS_ERROR) {
+ this.code = CloseFrame.NOCODE;
+ this.reason = "";
}
+ updatePayload();
+ }
- /**
- * Set the close code for this close frame
- * @param code the close code
- */
- public void setCode(int code) {
- this.code = code;
- // CloseFrame.TLS_ERROR is not allowed to be transfered over the wire
- if (code == CloseFrame.TLS_ERROR) {
- this.code = CloseFrame.NOCODE;
- this.reason = "";
- }
- updatePayload();
+ /**
+ * Set the close reason for this close frame
+ *
+ * @param reason the reason code
+ */
+ public void setReason(String reason) {
+ if (reason == null) {
+ reason = "";
}
+ this.reason = reason;
+ updatePayload();
+ }
+
+ /**
+ * Get the used close code
+ *
+ * @return the used close code
+ */
+ public int getCloseCode() {
+ return code;
+ }
+
+ /**
+ * Get the message that closeframe is containing
+ *
+ * @return the message in this frame
+ */
+ public String getMessage() {
+ return reason;
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + "code: " + code;
+ }
- /**
- * Set the close reason for this close frame
- * @param reason the reason code
- */
- public void setReason(String reason) {
- if (reason == null) {
- reason = "";
- }
- this.reason = reason;
- updatePayload();
+ @Override
+ public void isValid() throws InvalidDataException {
+ super.isValid();
+ if (code == CloseFrame.NO_UTF8 && reason.isEmpty()) {
+ throw new InvalidDataException(CloseFrame.NO_UTF8, "Received text is no valid utf8 string!");
}
- /**
- * Get the used close code
- *
- * @return the used close code
- */
- public int getCloseCode() {
- return code;
+ if (code == CloseFrame.NOCODE && 0 < reason.length()) {
+ throw new InvalidDataException(PROTOCOL_ERROR,
+ "A close frame must have a closecode if it has a reason");
}
-
- /**
- * Get the message that closeframe is containing
- *
- * @return the message in this frame
- */
- public String getMessage() {
- return reason;
+ //Intentional check for code != CloseFrame.TLS_ERROR just to make sure even if the code earlier changes
+ if ((code > CloseFrame.TLS_ERROR && code < 3000)) {
+ throw new InvalidDataException(PROTOCOL_ERROR, "Trying to send an illegal close code!");
+ }
+ if (code == CloseFrame.ABNORMAL_CLOSE || code == CloseFrame.TLS_ERROR
+ || code == CloseFrame.NOCODE || code > 4999 || code < 1000 || code == 1004) {
+ throw new InvalidFrameException("closecode must not be sent over the wire: " + code);
}
+ }
- @Override
- public String toString() {
- return super.toString() + "code: " + code;
+ @Override
+ public void setPayload(ByteBuffer payload) {
+ code = CloseFrame.NOCODE;
+ reason = "";
+ payload.mark();
+ if (payload.remaining() == 0) {
+ code = CloseFrame.NORMAL;
+ } else if (payload.remaining() == 1) {
+ code = CloseFrame.PROTOCOL_ERROR;
+ } else {
+ if (payload.remaining() >= 2) {
+ ByteBuffer bb = ByteBuffer.allocate(4);
+ bb.position(2);
+ bb.putShort(payload.getShort());
+ bb.position(0);
+ code = bb.getInt();
+ }
+ payload.reset();
+ try {
+ int mark = payload.position();// because stringUtf8 also creates a mark
+ validateUtf8(payload, mark);
+ } catch (InvalidDataException e) {
+ code = CloseFrame.NO_UTF8;
+ reason = null;
+ }
}
+ }
- @Override
- public void isValid() throws InvalidDataException {
- super.isValid();
- if (code == CloseFrame.NO_UTF8 && reason == null) {
- throw new InvalidDataException( CloseFrame.NO_UTF8, "Received text is no valid utf8 string!");
- }
- if (code == CloseFrame.NOCODE && 0 < reason.length()) {
- throw new InvalidDataException(PROTOCOL_ERROR, "A close frame must have a closecode if it has a reason");
- }
- //Intentional check for code != CloseFrame.TLS_ERROR just to make sure even if the code earlier changes
- if ((code > CloseFrame.TLS_ERROR && code < 3000)) {
- throw new InvalidDataException(PROTOCOL_ERROR, "Trying to send an illegal close code!");
- }
- if (code == CloseFrame.ABNORMAL_CLOSE || code == CloseFrame.TLS_ERROR || code == CloseFrame.NOCODE || code > 4999 || code < 1000 || code == 1004) {
- throw new InvalidFrameException("closecode must not be sent over the wire: " + code);
- }
+ /**
+ * Validate the payload to valid utf8
+ *
+ * @param mark the current mark
+ * @param payload the current payload
+ * @throws InvalidDataException the current payload is not a valid utf8
+ */
+ private void validateUtf8(ByteBuffer payload, int mark) throws InvalidDataException {
+ try {
+ payload.position(payload.position() + 2);
+ reason = Charsetfunctions.stringUtf8(payload);
+ } catch (IllegalArgumentException e) {
+ throw new InvalidDataException(CloseFrame.NO_UTF8);
+ } finally {
+ payload.position(mark);
}
+ }
- @Override
- public void setPayload(ByteBuffer payload) {
- code = CloseFrame.NOCODE;
- reason = "";
- payload.mark();
- if( payload.remaining() == 0 ) {
- code = CloseFrame.NORMAL;
- } else if( payload.remaining() == 1 ) {
- code = CloseFrame.PROTOCOL_ERROR;
- } else {
- if( payload.remaining() >= 2 ) {
- ByteBuffer bb = ByteBuffer.allocate( 4 );
- bb.position( 2 );
- bb.putShort( payload.getShort() );
- bb.position( 0 );
- code = bb.getInt();
- }
- payload.reset();
- try {
- int mark = payload.position();// because stringUtf8 also creates a mark
- try {
- payload.position( payload.position() + 2 );
- reason = Charsetfunctions.stringUtf8( payload );
- } catch ( IllegalArgumentException e ) {
- throw new InvalidDataException( CloseFrame.NO_UTF8 );
- } finally {
- payload.position( mark );
- }
- } catch ( InvalidDataException e ) {
- code = CloseFrame.NO_UTF8;
- reason = null;
- }
- }
- }
+ /**
+ * Update the payload to represent the close code and the reason
+ */
+ private void updatePayload() {
+ byte[] by = Charsetfunctions.utf8Bytes(reason);
+ ByteBuffer buf = ByteBuffer.allocate(4);
+ buf.putInt(code);
+ buf.position(2);
+ ByteBuffer pay = ByteBuffer.allocate(2 + by.length);
+ pay.put(buf);
+ pay.put(by);
+ pay.rewind();
+ super.setPayload(pay);
+ }
+
+ @Override
+ public ByteBuffer getPayloadData() {
+ if (code == NOCODE) {
+ return ByteBufferUtils.getEmptyByteBuffer();
+ }
+ return super.getPayloadData();
+ }
- /**
- * Update the payload to represent the close code and the reason
- */
- private void updatePayload() {
- byte[] by = Charsetfunctions.utf8Bytes(reason);
- ByteBuffer buf = ByteBuffer.allocate(4);
- buf.putInt(code);
- buf.position(2);
- ByteBuffer pay = ByteBuffer.allocate(2 + by.length);
- pay.put(buf);
- pay.put(by);
- pay.rewind();
- super.setPayload(pay);
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
}
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ if (!super.equals(o)) {
+ return false;
+ }
+
+ CloseFrame that = (CloseFrame) o;
- @Override
- public ByteBuffer getPayloadData() {
- if (code == NOCODE)
- return ByteBufferUtils.getEmptyByteBuffer();
- return super.getPayloadData();
+ if (code != that.code) {
+ return false;
}
+ return reason != null ? reason.equals(that.reason) : that.reason == null;
+ }
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + code;
+ result = 31 * result + (reason != null ? reason.hashCode() : 0);
+ return result;
+ }
}
diff --git a/src/main/java/org/java_websocket/framing/ContinuousFrame.java b/src/main/java/org/java_websocket/framing/ContinuousFrame.java
index f5082f090..c518cc3bd 100644
--- a/src/main/java/org/java_websocket/framing/ContinuousFrame.java
+++ b/src/main/java/org/java_websocket/framing/ContinuousFrame.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -25,15 +25,17 @@
package org.java_websocket.framing;
+import org.java_websocket.enums.Opcode;
+
/**
* Class to represent a continuous frame
*/
public class ContinuousFrame extends DataFrame {
- /**
- * constructor which sets the opcode of this frame to continuous
- */
- public ContinuousFrame() {
- super( Opcode.CONTINUOUS );
- }
+ /**
+ * constructor which sets the opcode of this frame to continuous
+ */
+ public ContinuousFrame() {
+ super(Opcode.CONTINUOUS);
+ }
}
diff --git a/src/main/java/org/java_websocket/framing/ControlFrame.java b/src/main/java/org/java_websocket/framing/ControlFrame.java
index f7b161d6a..1469dc5e6 100644
--- a/src/main/java/org/java_websocket/framing/ControlFrame.java
+++ b/src/main/java/org/java_websocket/framing/ControlFrame.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -25,6 +25,7 @@
package org.java_websocket.framing;
+import org.java_websocket.enums.Opcode;
import org.java_websocket.exceptions.InvalidDataException;
import org.java_websocket.exceptions.InvalidFrameException;
@@ -33,27 +34,28 @@
*/
public abstract class ControlFrame extends FramedataImpl1 {
- /**
- * Class to represent a control frame
- * @param opcode the opcode to use
- */
- public ControlFrame( Opcode opcode ) {
- super( opcode );
- }
+ /**
+ * Class to represent a control frame
+ *
+ * @param opcode the opcode to use
+ */
+ public ControlFrame(Opcode opcode) {
+ super(opcode);
+ }
- @Override
- public void isValid() throws InvalidDataException {
- if( !isFin() ) {
- throw new InvalidFrameException( "Control frame cant have fin==false set" );
- }
- if( isRSV1() ) {
- throw new InvalidFrameException( "Control frame cant have rsv1==true set" );
- }
- if( isRSV2() ) {
- throw new InvalidFrameException( "Control frame cant have rsv2==true set" );
- }
- if( isRSV3() ) {
- throw new InvalidFrameException( "Control frame cant have rsv3==true set" );
- }
- }
+ @Override
+ public void isValid() throws InvalidDataException {
+ if (!isFin()) {
+ throw new InvalidFrameException("Control frame can't have fin==false set");
+ }
+ if (isRSV1()) {
+ throw new InvalidFrameException("Control frame can't have rsv1==true set");
+ }
+ if (isRSV2()) {
+ throw new InvalidFrameException("Control frame can't have rsv2==true set");
+ }
+ if (isRSV3()) {
+ throw new InvalidFrameException("Control frame can't have rsv3==true set");
+ }
+ }
}
diff --git a/src/main/java/org/java_websocket/framing/DataFrame.java b/src/main/java/org/java_websocket/framing/DataFrame.java
index 428b23b1f..c845c2ced 100644
--- a/src/main/java/org/java_websocket/framing/DataFrame.java
+++ b/src/main/java/org/java_websocket/framing/DataFrame.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -25,6 +25,7 @@
package org.java_websocket.framing;
+import org.java_websocket.enums.Opcode;
import org.java_websocket.exceptions.InvalidDataException;
/**
@@ -32,17 +33,17 @@
*/
public abstract class DataFrame extends FramedataImpl1 {
- /**
- * Class to represent a data frame
- * @param opcode the opcode to use
- */
- public DataFrame(Opcode opcode) {
- super(opcode);
- }
+ /**
+ * Class to represent a data frame
+ *
+ * @param opcode the opcode to use
+ */
+ public DataFrame(Opcode opcode) {
+ super(opcode);
+ }
- @Override
- public void isValid() throws InvalidDataException
- {
- //Nothing specific to check
- }
+ @Override
+ public void isValid() throws InvalidDataException {
+ //Nothing specific to check
+ }
}
diff --git a/src/main/java/org/java_websocket/framing/Framedata.java b/src/main/java/org/java_websocket/framing/Framedata.java
index 31d02ee90..d3e8f3cbe 100644
--- a/src/main/java/org/java_websocket/framing/Framedata.java
+++ b/src/main/java/org/java_websocket/framing/Framedata.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -26,66 +26,69 @@
package org.java_websocket.framing;
import java.nio.ByteBuffer;
+import org.java_websocket.enums.Opcode;
/**
* The interface for the frame
*/
public interface Framedata {
- /**
- * Enum which contains the different valid opcodes
- */
- enum Opcode {
- CONTINUOUS, TEXT, BINARY, PING, PONG, CLOSING
- // more to come
- }
- /**
- * Indicates that this is the final fragment in a message. The first fragment MAY also be the final fragment.
- * @return true, if this frame is the final fragment
- */
- boolean isFin();
+ /**
+ * Indicates that this is the final fragment in a message. The first fragment MAY also be the
+ * final fragment.
+ *
+ * @return true, if this frame is the final fragment
+ */
+ boolean isFin();
- /**
- * Indicates that this frame has the rsv1 bit set.
- * @return true, if this frame has the rsv1 bit set
- */
- boolean isRSV1();
+ /**
+ * Indicates that this frame has the rsv1 bit set.
+ *
+ * @return true, if this frame has the rsv1 bit set
+ */
+ boolean isRSV1();
- /**
- * Indicates that this frame has the rsv2 bit set.
- * @return true, if this frame has the rsv2 bit set
- */
- boolean isRSV2();
+ /**
+ * Indicates that this frame has the rsv2 bit set.
+ *
+ * @return true, if this frame has the rsv2 bit set
+ */
+ boolean isRSV2();
- /**
- * Indicates that this frame has the rsv3 bit set.
- * @return true, if this frame has the rsv3 bit set
- */
- boolean isRSV3();
+ /**
+ * Indicates that this frame has the rsv3 bit set.
+ *
+ * @return true, if this frame has the rsv3 bit set
+ */
+ boolean isRSV3();
- /**
- * Defines whether the "Payload data" is masked.
- * @return true, "Payload data" is masked
- */
- boolean getTransfereMasked();
+ /**
+ * Defines whether the "Payload data" is masked.
+ *
+ * @return true, "Payload data" is masked
+ */
+ boolean getTransfereMasked();
- /**
- * Defines the interpretation of the "Payload data".
- * @return the interpretation as a Opcode
- */
- Opcode getOpcode();
+ /**
+ * Defines the interpretation of the "Payload data".
+ *
+ * @return the interpretation as a Opcode
+ */
+ Opcode getOpcode();
- /**
- * The "Payload data" which was sent in this frame
- * @return the "Payload data" as ByteBuffer
- */
- ByteBuffer getPayloadData();// TODO the separation of the application data and the extension data is yet to be done
+ /**
+ * The "Payload data" which was sent in this frame
+ *
+ * @return the "Payload data" as ByteBuffer
+ */
+ ByteBuffer getPayloadData();// TODO the separation of the application data and the extension data is yet to be done
- /**
- * Appends an additional frame to the current frame
- *
- * This methods does not override the opcode, but does override the fin
- * @param nextframe the additional frame
- */
- void append( Framedata nextframe );
+ /**
+ * Appends an additional frame to the current frame
+ *
+ * This methods does not override the opcode, but does override the fin
+ *
+ * @param nextframe the additional frame
+ */
+ void append(Framedata nextframe);
}
diff --git a/src/main/java/org/java_websocket/framing/FramedataImpl1.java b/src/main/java/org/java_websocket/framing/FramedataImpl1.java
index 2560d16d8..fc74f7aa2 100644
--- a/src/main/java/org/java_websocket/framing/FramedataImpl1.java
+++ b/src/main/java/org/java_websocket/framing/FramedataImpl1.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -25,249 +25,270 @@
package org.java_websocket.framing;
+import java.nio.ByteBuffer;
+import org.java_websocket.enums.Opcode;
import org.java_websocket.exceptions.InvalidDataException;
import org.java_websocket.util.ByteBufferUtils;
-import java.nio.ByteBuffer;
-
/**
* Abstract implementation of a frame
*/
public abstract class FramedataImpl1 implements Framedata {
- /**
- * Indicates that this is the final fragment in a message.
- */
- private boolean fin;
- /**
- * Defines the interpretation of the "Payload data".
- */
- private Opcode optcode;
-
- /**
- * The unmasked "Payload data" which was sent in this frame
- */
- private ByteBuffer unmaskedpayload;
-
- /**
- * Defines whether the "Payload data" is masked.
- */
- private boolean transferemasked;
-
- /**
- * Indicates that the rsv1 bit is set or not
- */
- private boolean rsv1;
-
- /**
- * Indicates that the rsv2 bit is set or not
- */
- private boolean rsv2;
-
- /**
- * Indicates that the rsv3 bit is set or not
- */
- private boolean rsv3;
-
- /**
- * Check if the frame is valid due to specification
- *
- * @throws InvalidDataException thrown if the frame is not a valid frame
- */
- public abstract void isValid() throws InvalidDataException;
-
- /**
- * Constructor for a FramedataImpl without any attributes set apart from the opcode
- *
- * @param op the opcode to use
- */
- public FramedataImpl1(Opcode op) {
- optcode = op;
- unmaskedpayload = ByteBufferUtils.getEmptyByteBuffer();
- fin = true;
- transferemasked = false;
- rsv1 = false;
- rsv2 = false;
- rsv3 = false;
+ /**
+ * Indicates that this is the final fragment in a message.
+ */
+ private boolean fin;
+ /**
+ * Defines the interpretation of the "Payload data".
+ */
+ private Opcode optcode;
+
+ /**
+ * The unmasked "Payload data" which was sent in this frame
+ */
+ private ByteBuffer unmaskedpayload;
+
+ /**
+ * Defines whether the "Payload data" is masked.
+ */
+ private boolean transferemasked;
+
+ /**
+ * Indicates that the rsv1 bit is set or not
+ */
+ private boolean rsv1;
+
+ /**
+ * Indicates that the rsv2 bit is set or not
+ */
+ private boolean rsv2;
+
+ /**
+ * Indicates that the rsv3 bit is set or not
+ */
+ private boolean rsv3;
+
+ /**
+ * Check if the frame is valid due to specification
+ *
+ * @throws InvalidDataException thrown if the frame is not a valid frame
+ */
+ public abstract void isValid() throws InvalidDataException;
+
+ /**
+ * Constructor for a FramedataImpl without any attributes set apart from the opcode
+ *
+ * @param op the opcode to use
+ */
+ public FramedataImpl1(Opcode op) {
+ optcode = op;
+ unmaskedpayload = ByteBufferUtils.getEmptyByteBuffer();
+ fin = true;
+ transferemasked = false;
+ rsv1 = false;
+ rsv2 = false;
+ rsv3 = false;
+ }
+
+ @Override
+ public boolean isRSV1() {
+ return rsv1;
+ }
+
+ @Override
+ public boolean isRSV2() {
+ return rsv2;
+ }
+
+ @Override
+ public boolean isRSV3() {
+ return rsv3;
+ }
+
+ @Override
+ public boolean isFin() {
+ return fin;
+ }
+
+ @Override
+ public Opcode getOpcode() {
+ return optcode;
+ }
+
+ @Override
+ public boolean getTransfereMasked() {
+ return transferemasked;
+ }
+
+ @Override
+ public ByteBuffer getPayloadData() {
+ return unmaskedpayload;
+ }
+
+ @Override
+ public void append(Framedata nextframe) {
+ ByteBuffer b = nextframe.getPayloadData();
+ if (unmaskedpayload == null) {
+ unmaskedpayload = ByteBuffer.allocate(b.remaining());
+ b.mark();
+ unmaskedpayload.put(b);
+ b.reset();
+ } else {
+ b.mark();
+ unmaskedpayload.position(unmaskedpayload.limit());
+ unmaskedpayload.limit(unmaskedpayload.capacity());
+
+ if (b.remaining() > unmaskedpayload.remaining()) {
+ ByteBuffer tmp = ByteBuffer.allocate(b.remaining() + unmaskedpayload.capacity());
+ unmaskedpayload.flip();
+ tmp.put(unmaskedpayload);
+ tmp.put(b);
+ unmaskedpayload = tmp;
+
+ } else {
+ unmaskedpayload.put(b);
+ }
+ unmaskedpayload.rewind();
+ b.reset();
}
-
- @Override
- public boolean isRSV1() {
- return rsv1;
+ fin = nextframe.isFin();
+
+ }
+
+ @Override
+ public String toString() {
+ return "Framedata{ opcode:" + getOpcode() + ", fin:" + isFin() + ", rsv1:" + isRSV1()
+ + ", rsv2:" + isRSV2() + ", rsv3:" + isRSV3() + ", payload length:[pos:" + unmaskedpayload
+ .position() + ", len:" + unmaskedpayload.remaining() + "], payload:" + (
+ unmaskedpayload.remaining() > 1000 ? "(too big to display)"
+ : new String(unmaskedpayload.array())) + '}';
+ }
+
+ /**
+ * Set the payload of this frame to the provided payload
+ *
+ * @param payload the payload which is to set
+ */
+ public void setPayload(ByteBuffer payload) {
+ this.unmaskedpayload = payload;
+ }
+
+ /**
+ * Set the fin of this frame to the provided boolean
+ *
+ * @param fin true if fin has to be set
+ */
+ public void setFin(boolean fin) {
+ this.fin = fin;
+ }
+
+ /**
+ * Set the rsv1 of this frame to the provided boolean
+ *
+ * @param rsv1 true if rsv1 has to be set
+ */
+ public void setRSV1(boolean rsv1) {
+ this.rsv1 = rsv1;
+ }
+
+ /**
+ * Set the rsv2 of this frame to the provided boolean
+ *
+ * @param rsv2 true if rsv2 has to be set
+ */
+ public void setRSV2(boolean rsv2) {
+ this.rsv2 = rsv2;
+ }
+
+ /**
+ * Set the rsv3 of this frame to the provided boolean
+ *
+ * @param rsv3 true if rsv3 has to be set
+ */
+ public void setRSV3(boolean rsv3) {
+ this.rsv3 = rsv3;
+ }
+
+ /**
+ * Set the tranferemask of this frame to the provided boolean
+ *
+ * @param transferemasked true if transferemasked has to be set
+ */
+ public void setTransferemasked(boolean transferemasked) {
+ this.transferemasked = transferemasked;
+ }
+
+ /**
+ * Get a frame with a specific opcode
+ *
+ * @param opcode the opcode representing the frame
+ * @return the frame with a specific opcode
+ */
+ public static FramedataImpl1 get(Opcode opcode) {
+ if (opcode == null) {
+ throw new IllegalArgumentException("Supplied opcode cannot be null");
}
-
- @Override
- public boolean isRSV2() {
- return rsv2;
+ switch (opcode) {
+ case PING:
+ return new PingFrame();
+ case PONG:
+ return new PongFrame();
+ case TEXT:
+ return new TextFrame();
+ case BINARY:
+ return new BinaryFrame();
+ case CLOSING:
+ return new CloseFrame();
+ case CONTINUOUS:
+ return new ContinuousFrame();
+ default:
+ throw new IllegalArgumentException("Supplied opcode is invalid");
}
+ }
- @Override
- public boolean isRSV3() {
- return rsv3;
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
}
-
- @Override
- public boolean isFin() {
- return fin;
+ if (o == null || getClass() != o.getClass()) {
+ return false;
}
- @Override
- public Opcode getOpcode() {
- return optcode;
- }
+ FramedataImpl1 that = (FramedataImpl1) o;
- @Override
- public boolean getTransfereMasked() {
- return transferemasked;
+ if (fin != that.fin) {
+ return false;
}
-
- @Override
- public ByteBuffer getPayloadData() {
- return unmaskedpayload;
+ if (transferemasked != that.transferemasked) {
+ return false;
}
-
- @Override
- public void append(Framedata nextframe) {
- ByteBuffer b = nextframe.getPayloadData();
- if (unmaskedpayload == null) {
- unmaskedpayload = ByteBuffer.allocate(b.remaining());
- b.mark();
- unmaskedpayload.put(b);
- b.reset();
- } else {
- b.mark();
- unmaskedpayload.position(unmaskedpayload.limit());
- unmaskedpayload.limit(unmaskedpayload.capacity());
-
- if (b.remaining() > unmaskedpayload.remaining()) {
- ByteBuffer tmp = ByteBuffer.allocate(b.remaining() + unmaskedpayload.capacity());
- unmaskedpayload.flip();
- tmp.put(unmaskedpayload);
- tmp.put(b);
- unmaskedpayload = tmp;
-
- } else {
- unmaskedpayload.put(b);
- }
- unmaskedpayload.rewind();
- b.reset();
- }
- fin = nextframe.isFin();
-
+ if (rsv1 != that.rsv1) {
+ return false;
}
-
- @Override
- public String toString() {
- return "Framedata{ optcode:" + getOpcode() + ", fin:" + isFin() + ", rsv1:" + isRSV1() + ", rsv2:" + isRSV2() + ", rsv3:" + isRSV3() + ", payloadlength:[pos:" + unmaskedpayload.position() + ", len:" + unmaskedpayload.remaining() + "], payload:" + ( unmaskedpayload.remaining() > 1000 ? "(too big to display)" : new String( unmaskedpayload.array() ) ) + '}';
+ if (rsv2 != that.rsv2) {
+ return false;
}
-
- /**
- * Set the payload of this frame to the provided payload
- *
- * @param payload the payload which is to set
- */
- public void setPayload(ByteBuffer payload) {
- this.unmaskedpayload = payload;
+ if (rsv3 != that.rsv3) {
+ return false;
}
-
- /**
- * Set the fin of this frame to the provided boolean
- *
- * @param fin true if fin has to be set
- */
- public void setFin(boolean fin) {
- this.fin = fin;
- }
-
- /**
- * Set the rsv1 of this frame to the provided boolean
- *
- * @param rsv1 true if fin has to be set
- */
- public void setRSV1(boolean rsv1) {
- this.rsv1 = rsv1;
- }
-
- /**
- * Set the rsv2 of this frame to the provided boolean
- *
- * @param rsv2 true if fin has to be set
- */
- public void setRSV2(boolean rsv2) {
- this.rsv2 = rsv2;
- }
-
- /**
- * Set the rsv3 of this frame to the provided boolean
- *
- * @param rsv3 true if fin has to be set
- */
- public void setRSV3(boolean rsv3) {
- this.rsv3 = rsv3;
- }
-
- /**
- * Set the tranferemask of this frame to the provided boolean
- *
- * @param transferemasked true if transferemasked has to be set
- */
- public void setTransferemasked(boolean transferemasked) {
- this.transferemasked = transferemasked;
- }
-
- /**
- * Get a frame with a specific opcode
- *
- * @param opcode the opcode representing the frame
- * @return the frame with a specific opcode
- */
- public static FramedataImpl1 get(Opcode opcode) {
- if (opcode== null) {
- throw new IllegalArgumentException("Supplied opcode cannot be null");
- }
- switch (opcode) {
- case PING:
- return new PingFrame();
- case PONG:
- return new PongFrame();
- case TEXT:
- return new TextFrame();
- case BINARY:
- return new BinaryFrame();
- case CLOSING:
- return new CloseFrame();
- case CONTINUOUS:
- return new ContinuousFrame();
- default:
- throw new IllegalArgumentException("Supplied opcode is invalid");
- }
- }
-
- @Override
- public boolean equals( Object o ) {
- if( this == o ) return true;
- if( o == null || getClass() != o.getClass() ) return false;
-
- FramedataImpl1 that = ( FramedataImpl1 ) o;
-
- if( fin != that.fin ) return false;
- if( transferemasked != that.transferemasked ) return false;
- if( rsv1 != that.rsv1 ) return false;
- if( rsv2 != that.rsv2 ) return false;
- if( rsv3 != that.rsv3 ) return false;
- if( optcode != that.optcode ) return false;
- return unmaskedpayload != null ? unmaskedpayload.equals( that.unmaskedpayload ) : that.unmaskedpayload == null;
- }
-
- @Override
- public int hashCode() {
- int result = ( fin ? 1 : 0 );
- result = 31 * result + optcode.hashCode();
- result = 31 * result + ( unmaskedpayload != null ? unmaskedpayload.hashCode() : 0 );
- result = 31 * result + ( transferemasked ? 1 : 0 );
- result = 31 * result + ( rsv1 ? 1 : 0 );
- result = 31 * result + ( rsv2 ? 1 : 0 );
- result = 31 * result + ( rsv3 ? 1 : 0 );
- return result;
+ if (optcode != that.optcode) {
+ return false;
}
+ return unmaskedpayload != null ? unmaskedpayload.equals(that.unmaskedpayload)
+ : that.unmaskedpayload == null;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = (fin ? 1 : 0);
+ result = 31 * result + optcode.hashCode();
+ result = 31 * result + (unmaskedpayload != null ? unmaskedpayload.hashCode() : 0);
+ result = 31 * result + (transferemasked ? 1 : 0);
+ result = 31 * result + (rsv1 ? 1 : 0);
+ result = 31 * result + (rsv2 ? 1 : 0);
+ result = 31 * result + (rsv3 ? 1 : 0);
+ return result;
+ }
}
diff --git a/src/main/java/org/java_websocket/framing/PingFrame.java b/src/main/java/org/java_websocket/framing/PingFrame.java
index 3cf6b6ede..ae2b29119 100644
--- a/src/main/java/org/java_websocket/framing/PingFrame.java
+++ b/src/main/java/org/java_websocket/framing/PingFrame.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -25,15 +25,17 @@
package org.java_websocket.framing;
+import org.java_websocket.enums.Opcode;
+
/**
* Class to represent a ping frame
*/
public class PingFrame extends ControlFrame {
- /**
- * constructor which sets the opcode of this frame to ping
- */
- public PingFrame() {
- super(Opcode.PING);
- }
+ /**
+ * constructor which sets the opcode of this frame to ping
+ */
+ public PingFrame() {
+ super(Opcode.PING);
+ }
}
diff --git a/src/main/java/org/java_websocket/framing/PongFrame.java b/src/main/java/org/java_websocket/framing/PongFrame.java
index d86c73eda..4b58139ea 100644
--- a/src/main/java/org/java_websocket/framing/PongFrame.java
+++ b/src/main/java/org/java_websocket/framing/PongFrame.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -25,25 +25,27 @@
package org.java_websocket.framing;
+import org.java_websocket.enums.Opcode;
+
/**
* Class to represent a pong frame
*/
public class PongFrame extends ControlFrame {
- /**
- * constructor which sets the opcode of this frame to pong
- */
- public PongFrame() {
- super(Opcode.PONG);
- }
+ /**
+ * constructor which sets the opcode of this frame to pong
+ */
+ public PongFrame() {
+ super(Opcode.PONG);
+ }
- /**
- * constructor which sets the opcode of this frame to ping copying over the payload of the ping
- *
- * @param pingFrame the PingFrame which payload is to copy
- */
- public PongFrame(PingFrame pingFrame) {
- super(Opcode.PONG);
- setPayload(pingFrame.getPayloadData());
- }
+ /**
+ * constructor which sets the opcode of this frame to ping copying over the payload of the ping
+ *
+ * @param pingFrame the PingFrame which payload is to copy
+ */
+ public PongFrame(PingFrame pingFrame) {
+ super(Opcode.PONG);
+ setPayload(pingFrame.getPayloadData());
+ }
}
diff --git a/src/main/java/org/java_websocket/framing/TextFrame.java b/src/main/java/org/java_websocket/framing/TextFrame.java
index e9a73e95a..52154b47e 100644
--- a/src/main/java/org/java_websocket/framing/TextFrame.java
+++ b/src/main/java/org/java_websocket/framing/TextFrame.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -25,6 +25,7 @@
package org.java_websocket.framing;
+import org.java_websocket.enums.Opcode;
import org.java_websocket.exceptions.InvalidDataException;
import org.java_websocket.util.Charsetfunctions;
@@ -33,18 +34,18 @@
*/
public class TextFrame extends DataFrame {
- /**
- * constructor which sets the opcode of this frame to text
- */
- public TextFrame() {
- super(Opcode.TEXT);
- }
+ /**
+ * constructor which sets the opcode of this frame to text
+ */
+ public TextFrame() {
+ super(Opcode.TEXT);
+ }
- @Override
- public void isValid() throws InvalidDataException {
- super.isValid();
- if (!Charsetfunctions.isValidUTF8( getPayloadData() )) {
- throw new InvalidDataException(CloseFrame.NO_UTF8, "Received text is no valid utf8 string!");
- }
+ @Override
+ public void isValid() throws InvalidDataException {
+ super.isValid();
+ if (!Charsetfunctions.isValidUTF8(getPayloadData())) {
+ throw new InvalidDataException(CloseFrame.NO_UTF8, "Received text is no valid utf8 string!");
}
+ }
}
diff --git a/src/main/java/org/java_websocket/framing/package-info.java b/src/main/java/org/java_websocket/framing/package-info.java
index d4291a217..12e1510ec 100644
--- a/src/main/java/org/java_websocket/framing/package-info.java
+++ b/src/main/java/org/java_websocket/framing/package-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -24,6 +24,7 @@
*/
/**
- * This package encapsulates all interfaces and implementations in relation with the WebSocket frames.
+ * This package encapsulates all interfaces and implementations in relation with the WebSocket
+ * frames.
*/
package org.java_websocket.framing;
\ No newline at end of file
diff --git a/src/main/java/org/java_websocket/handshake/ClientHandshake.java b/src/main/java/org/java_websocket/handshake/ClientHandshake.java
index 2a3b1bc2d..f0cbc3ab9 100644
--- a/src/main/java/org/java_websocket/handshake/ClientHandshake.java
+++ b/src/main/java/org/java_websocket/handshake/ClientHandshake.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -30,9 +30,10 @@
*/
public interface ClientHandshake extends Handshakedata {
- /**
- * returns the HTTP Request-URI as defined by http://tools.ietf.org/html/rfc2616#section-5.1.2
- * @return the HTTP Request-URI
- */
- String getResourceDescriptor();
+ /**
+ * returns the HTTP Request-URI as defined by http://tools.ietf.org/html/rfc2616#section-5.1.2
+ *
+ * @return the HTTP Request-URI
+ */
+ String getResourceDescriptor();
}
diff --git a/src/main/java/org/java_websocket/handshake/ClientHandshakeBuilder.java b/src/main/java/org/java_websocket/handshake/ClientHandshakeBuilder.java
index 388a7646b..81875153d 100644
--- a/src/main/java/org/java_websocket/handshake/ClientHandshakeBuilder.java
+++ b/src/main/java/org/java_websocket/handshake/ClientHandshakeBuilder.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -30,9 +30,10 @@
*/
public interface ClientHandshakeBuilder extends HandshakeBuilder, ClientHandshake {
- /**
- * Set a specific resource descriptor
- * @param resourceDescriptor the resource descriptior to set
- */
- void setResourceDescriptor( String resourceDescriptor );
+ /**
+ * Set a specific resource descriptor
+ *
+ * @param resourceDescriptor the resource descriptior to set
+ */
+ void setResourceDescriptor(String resourceDescriptor);
}
diff --git a/src/main/java/org/java_websocket/handshake/HandshakeBuilder.java b/src/main/java/org/java_websocket/handshake/HandshakeBuilder.java
index b11c02117..1f4de2067 100644
--- a/src/main/java/org/java_websocket/handshake/HandshakeBuilder.java
+++ b/src/main/java/org/java_websocket/handshake/HandshakeBuilder.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -30,16 +30,18 @@
*/
public interface HandshakeBuilder extends Handshakedata {
- /**
- * Setter for the content of the handshake
- * @param content the content to set
- */
- void setContent( byte[] content );
+ /**
+ * Setter for the content of the handshake
+ *
+ * @param content the content to set
+ */
+ void setContent(byte[] content);
- /**
- * Adding a specific field with a specific value
- * @param name the http field
- * @param value the value for this field
- */
- void put( String name, String value );
+ /**
+ * Adding a specific field with a specific value
+ *
+ * @param name the http field
+ * @param value the value for this field
+ */
+ void put(String name, String value);
}
diff --git a/src/main/java/org/java_websocket/handshake/HandshakeImpl1Client.java b/src/main/java/org/java_websocket/handshake/HandshakeImpl1Client.java
index 180d3b650..11ffa43dd 100644
--- a/src/main/java/org/java_websocket/handshake/HandshakeImpl1Client.java
+++ b/src/main/java/org/java_websocket/handshake/HandshakeImpl1Client.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -30,20 +30,21 @@
*/
public class HandshakeImpl1Client extends HandshakedataImpl1 implements ClientHandshakeBuilder {
- /**
- * Attribute for the resource descriptor
- */
- private String resourceDescriptor = "*";
+ /**
+ * Attribute for the resource descriptor
+ */
+ private String resourceDescriptor = "*";
- @Override
- public void setResourceDescriptor( String resourceDescriptor ) throws IllegalArgumentException {
- if(resourceDescriptor==null)
- throw new IllegalArgumentException( "http resource descriptor must not be null" );
- this.resourceDescriptor = resourceDescriptor;
- }
+ @Override
+ public void setResourceDescriptor(String resourceDescriptor) {
+ if (resourceDescriptor == null) {
+ throw new IllegalArgumentException("http resource descriptor must not be null");
+ }
+ this.resourceDescriptor = resourceDescriptor;
+ }
- @Override
- public String getResourceDescriptor() {
- return resourceDescriptor;
- }
+ @Override
+ public String getResourceDescriptor() {
+ return resourceDescriptor;
+ }
}
diff --git a/src/main/java/org/java_websocket/handshake/HandshakeImpl1Server.java b/src/main/java/org/java_websocket/handshake/HandshakeImpl1Server.java
index 4f38c9ec5..87540141d 100644
--- a/src/main/java/org/java_websocket/handshake/HandshakeImpl1Server.java
+++ b/src/main/java/org/java_websocket/handshake/HandshakeImpl1Server.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -30,33 +30,33 @@
*/
public class HandshakeImpl1Server extends HandshakedataImpl1 implements ServerHandshakeBuilder {
- /**
- * Attribute for the http status
- */
- private short httpstatus;
-
- /**
- * Attribute for the http status message
- */
- private String httpstatusmessage;
-
- @Override
- public String getHttpStatusMessage() {
- return httpstatusmessage;
- }
-
- @Override
- public short getHttpStatus() {
- return httpstatus;
- }
-
- @Override
- public void setHttpStatusMessage( String message ) {
- this.httpstatusmessage = message;
- }
-
- @Override
- public void setHttpStatus( short status ) {
- httpstatus = status;
- }
+ /**
+ * Attribute for the http status
+ */
+ private short httpstatus;
+
+ /**
+ * Attribute for the http status message
+ */
+ private String httpstatusmessage;
+
+ @Override
+ public String getHttpStatusMessage() {
+ return httpstatusmessage;
+ }
+
+ @Override
+ public short getHttpStatus() {
+ return httpstatus;
+ }
+
+ @Override
+ public void setHttpStatusMessage(String message) {
+ this.httpstatusmessage = message;
+ }
+
+ @Override
+ public void setHttpStatus(short status) {
+ httpstatus = status;
+ }
}
diff --git a/src/main/java/org/java_websocket/handshake/Handshakedata.java b/src/main/java/org/java_websocket/handshake/Handshakedata.java
index 3671628e3..fd270ecb6 100644
--- a/src/main/java/org/java_websocket/handshake/Handshakedata.java
+++ b/src/main/java/org/java_websocket/handshake/Handshakedata.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -32,29 +32,33 @@
*/
public interface Handshakedata {
- /**
- * Iterator for the http fields
- * @return the http fields
- */
- Iterator iterateHttpFields();
+ /**
+ * Iterator for the http fields
+ *
+ * @return the http fields
+ */
+ Iterator iterateHttpFields();
- /**
- * Gets the value of the field
- * @param name The name of the field
- * @return the value of the field or an empty String if not in the handshake
- */
- String getFieldValue( String name );
+ /**
+ * Gets the value of the field
+ *
+ * @param name The name of the field
+ * @return the value of the field or an empty String if not in the handshake
+ */
+ String getFieldValue(String name);
- /**
- * Checks if this handshake contains a specific field
- * @param name The name of the field
- * @return true, if it contains the field
- */
- boolean hasFieldValue( String name );
+ /**
+ * Checks if this handshake contains a specific field
+ *
+ * @param name The name of the field
+ * @return true, if it contains the field
+ */
+ boolean hasFieldValue(String name);
- /**
- * Get the content of the handshake
- * @return the content as byte-array
- */
- byte[] getContent();
+ /**
+ * Get the content of the handshake
+ *
+ * @return the content as byte-array
+ */
+ byte[] getContent();
}
diff --git a/src/main/java/org/java_websocket/handshake/HandshakedataImpl1.java b/src/main/java/org/java_websocket/handshake/HandshakedataImpl1.java
index c87396543..62824cdd2 100644
--- a/src/main/java/org/java_websocket/handshake/HandshakedataImpl1.java
+++ b/src/main/java/org/java_websocket/handshake/HandshakedataImpl1.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -34,54 +34,54 @@
*/
public class HandshakedataImpl1 implements HandshakeBuilder {
- /**
- * Attribute for the content of the handshake
- */
- private byte[] content;
+ /**
+ * Attribute for the content of the handshake
+ */
+ private byte[] content;
- /**
- * Attribute for the http fields and values
- */
- private TreeMap map;
+ /**
+ * Attribute for the http fields and values
+ */
+ private TreeMap map;
- /**
- * Constructor for handshake implementation
- */
- public HandshakedataImpl1() {
- map = new TreeMap( String.CASE_INSENSITIVE_ORDER );
- }
+ /**
+ * Constructor for handshake implementation
+ */
+ public HandshakedataImpl1() {
+ map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ }
- @Override
- public Iterator iterateHttpFields() {
- return Collections.unmodifiableSet( map.keySet() ).iterator();// Safety first
- }
+ @Override
+ public Iterator iterateHttpFields() {
+ return Collections.unmodifiableSet(map.keySet()).iterator();// Safety first
+ }
- @Override
- public String getFieldValue( String name ) {
- String s = map.get( name );
- if ( s == null ) {
- return "";
- }
- return s;
- }
+ @Override
+ public String getFieldValue(String name) {
+ String s = map.get(name);
+ if (s == null) {
+ return "";
+ }
+ return s;
+ }
- @Override
- public byte[] getContent() {
- return content;
- }
+ @Override
+ public byte[] getContent() {
+ return content;
+ }
- @Override
- public void setContent( byte[] content ) {
- this.content = content;
- }
+ @Override
+ public void setContent(byte[] content) {
+ this.content = content;
+ }
- @Override
- public void put( String name, String value ) {
- map.put( name, value );
- }
+ @Override
+ public void put(String name, String value) {
+ map.put(name, value);
+ }
- @Override
- public boolean hasFieldValue( String name ) {
- return map.containsKey( name );
- }
+ @Override
+ public boolean hasFieldValue(String name) {
+ return map.containsKey(name);
+ }
}
diff --git a/src/main/java/org/java_websocket/handshake/ServerHandshake.java b/src/main/java/org/java_websocket/handshake/ServerHandshake.java
index 63c8c0a28..1b5a5a9b8 100644
--- a/src/main/java/org/java_websocket/handshake/ServerHandshake.java
+++ b/src/main/java/org/java_websocket/handshake/ServerHandshake.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -30,15 +30,17 @@
*/
public interface ServerHandshake extends Handshakedata {
- /**
- * Get the http status code
- * @return the http status code
- */
- short getHttpStatus();
+ /**
+ * Get the http status code
+ *
+ * @return the http status code
+ */
+ short getHttpStatus();
- /**
- * Get the http status message
- * @return the http status message
- */
- String getHttpStatusMessage();
+ /**
+ * Get the http status message
+ *
+ * @return the http status message
+ */
+ String getHttpStatusMessage();
}
diff --git a/src/main/java/org/java_websocket/handshake/ServerHandshakeBuilder.java b/src/main/java/org/java_websocket/handshake/ServerHandshakeBuilder.java
index 11e49e3f3..51212f048 100644
--- a/src/main/java/org/java_websocket/handshake/ServerHandshakeBuilder.java
+++ b/src/main/java/org/java_websocket/handshake/ServerHandshakeBuilder.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -30,15 +30,17 @@
*/
public interface ServerHandshakeBuilder extends HandshakeBuilder, ServerHandshake {
- /**
- * Setter for the http status code
- * @param status the http status code
- */
- void setHttpStatus( short status );
+ /**
+ * Setter for the http status code
+ *
+ * @param status the http status code
+ */
+ void setHttpStatus(short status);
- /**
- * Setter for the http status message
- * @param message the http status message
- */
- void setHttpStatusMessage( String message );
+ /**
+ * Setter for the http status message
+ *
+ * @param message the http status message
+ */
+ void setHttpStatusMessage(String message);
}
diff --git a/src/main/java/org/java_websocket/handshake/package-info.java b/src/main/java/org/java_websocket/handshake/package-info.java
index 3249afc13..7af26d497 100644
--- a/src/main/java/org/java_websocket/handshake/package-info.java
+++ b/src/main/java/org/java_websocket/handshake/package-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -24,7 +24,8 @@
*/
/**
- * This package encapsulates all interfaces and implementations in relation with the WebSocket handshake.
+ * This package encapsulates all interfaces and implementations in relation with the WebSocket
+ * handshake.
*/
package org.java_websocket.handshake;
diff --git a/src/main/java/org/java_websocket/interfaces/ISSLChannel.java b/src/main/java/org/java_websocket/interfaces/ISSLChannel.java
new file mode 100644
index 000000000..c783c5844
--- /dev/null
+++ b/src/main/java/org/java_websocket/interfaces/ISSLChannel.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2010-2020 Nathan Rajlich
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+package org.java_websocket.interfaces;
+
+import javax.net.ssl.SSLEngine;
+
+/**
+ * Interface which specifies all required methods a SSLSocketChannel has to make public.
+ *
+ * @since 1.4.1
+ */
+public interface ISSLChannel {
+
+ /**
+ * Get the ssl engine used for the de- and encryption of the communication.
+ *
+ * @return the ssl engine of this channel
+ */
+ SSLEngine getSSLEngine();
+}
diff --git a/src/main/java/org/java_websocket/interfaces/package-info.java b/src/main/java/org/java_websocket/interfaces/package-info.java
new file mode 100644
index 000000000..b70dbc174
--- /dev/null
+++ b/src/main/java/org/java_websocket/interfaces/package-info.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2010-2020 Nathan Rajlich
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+/**
+ * This package encapsulates all new interfaces.
+ */
+package org.java_websocket.interfaces;
\ No newline at end of file
diff --git a/src/main/java/org/java_websocket/protocols/IProtocol.java b/src/main/java/org/java_websocket/protocols/IProtocol.java
index a98134a0a..5300aed2a 100644
--- a/src/main/java/org/java_websocket/protocols/IProtocol.java
+++ b/src/main/java/org/java_websocket/protocols/IProtocol.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -32,36 +32,41 @@
*/
public interface IProtocol {
- /**
- * Check if the received Sec-WebSocket-Protocol header field contains a offer for the specific protocol
- *
- * @param inputProtocolHeader the received Sec-WebSocket-Protocol header field offered by the other endpoint
- * @return true, if the offer does fit to this specific protocol
- * @since 1.3.7
- */
- boolean acceptProvidedProtocol( String inputProtocolHeader );
+ /**
+ * Check if the received Sec-WebSocket-Protocol header field contains a offer for the specific
+ * protocol
+ *
+ * @param inputProtocolHeader the received Sec-WebSocket-Protocol header field offered by the
+ * other endpoint
+ * @return true, if the offer does fit to this specific protocol
+ * @since 1.3.7
+ */
+ boolean acceptProvidedProtocol(String inputProtocolHeader);
- /**
- * Return the specific Sec-WebSocket-protocol header offer for this protocol if the endpoint.
- * If the extension returns an empty string (""), the offer will not be included in the handshake.
- *
- * @return the specific Sec-WebSocket-Protocol header for this protocol
- * @since 1.3.7
- */
- String getProvidedProtocol();
+ /**
+ * Return the specific Sec-WebSocket-protocol header offer for this protocol if the endpoint. If
+ * the extension returns an empty string (""), the offer will not be included in the handshake.
+ *
+ * @return the specific Sec-WebSocket-Protocol header for this protocol
+ * @since 1.3.7
+ */
+ String getProvidedProtocol();
- /**
- * To prevent protocols to be used more than once the Websocket implementation should call this method in order to create a new usable version of a given protocol instance.
- * @return a copy of the protocol
- * @since 1.3.7
- */
- IProtocol copyInstance();
+ /**
+ * To prevent protocols to be used more than once the Websocket implementation should call this
+ * method in order to create a new usable version of a given protocol instance.
+ *
+ * @return a copy of the protocol
+ * @since 1.3.7
+ */
+ IProtocol copyInstance();
- /**
- * Return a string which should contain the protocol name as well as additional information about the current configurations for this protocol (DEBUG purposes)
- *
- * @return a string containing the protocol name as well as additional information
- * @since 1.3.7
- */
- String toString();
+ /**
+ * Return a string which should contain the protocol name as well as additional information about
+ * the current configurations for this protocol (DEBUG purposes)
+ *
+ * @return a string containing the protocol name as well as additional information
+ * @since 1.3.7
+ */
+ String toString();
}
diff --git a/src/main/java/org/java_websocket/protocols/Protocol.java b/src/main/java/org/java_websocket/protocols/Protocol.java
index 626c7167d..f7fbfb58b 100644
--- a/src/main/java/org/java_websocket/protocols/Protocol.java
+++ b/src/main/java/org/java_websocket/protocols/Protocol.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -25,6 +25,8 @@
package org.java_websocket.protocols;
+import java.util.regex.Pattern;
+
/**
* Class which represents the protocol used as Sec-WebSocket-Protocol
*
@@ -32,62 +34,72 @@
*/
public class Protocol implements IProtocol {
- /**
- * Attribute for the provided protocol
- */
- private final String providedProtocol;
+ private static final Pattern patternSpace = Pattern.compile(" ");
+ private static final Pattern patternComma = Pattern.compile(",");
+
+ /**
+ * Attribute for the provided protocol
+ */
+ private final String providedProtocol;
- /**
- * Constructor for a Sec-Websocket-Protocol
- *
- * @param providedProtocol the protocol string
- */
- public Protocol( String providedProtocol ) {
- if( providedProtocol == null ) {
- throw new IllegalArgumentException();
- }
- this.providedProtocol = providedProtocol;
- }
+ /**
+ * Constructor for a Sec-Websocket-Protocol
+ *
+ * @param providedProtocol the protocol string
+ */
+ public Protocol(String providedProtocol) {
+ if (providedProtocol == null) {
+ throw new IllegalArgumentException();
+ }
+ this.providedProtocol = providedProtocol;
+ }
- @Override
- public boolean acceptProvidedProtocol( String inputProtocolHeader ) {
- String protocolHeader = inputProtocolHeader.replaceAll( " ", "" );
- String[] headers = protocolHeader.split( "," );
- for( String header : headers ) {
- if( providedProtocol.equals( header ) ) {
- return true;
- }
- }
- return false;
- }
+ @Override
+ public boolean acceptProvidedProtocol(String inputProtocolHeader) {
+ if ("".equals(providedProtocol)) {
+ return true;
+ }
+ String protocolHeader = patternSpace.matcher(inputProtocolHeader).replaceAll("");
+ String[] headers = patternComma.split(protocolHeader);
+ for (String header : headers) {
+ if (providedProtocol.equals(header)) {
+ return true;
+ }
+ }
+ return false;
+ }
- @Override
- public String getProvidedProtocol() {
- return this.providedProtocol;
- }
+ @Override
+ public String getProvidedProtocol() {
+ return this.providedProtocol;
+ }
- @Override
- public IProtocol copyInstance() {
- return new Protocol( getProvidedProtocol() );
- }
+ @Override
+ public IProtocol copyInstance() {
+ return new Protocol(getProvidedProtocol());
+ }
- @Override
- public String toString() {
- return getProvidedProtocol();
- }
+ @Override
+ public String toString() {
+ return getProvidedProtocol();
+ }
- @Override
- public boolean equals( Object o ) {
- if( this == o ) return true;
- if( o == null || getClass() != o.getClass() ) return false;
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
- Protocol protocol = ( Protocol ) o;
+ Protocol protocol = (Protocol) o;
- return providedProtocol.equals( protocol.providedProtocol );
- }
+ return providedProtocol.equals(protocol.providedProtocol);
+ }
- @Override
- public int hashCode() {
- return providedProtocol.hashCode();
- }
+ @Override
+ public int hashCode() {
+ return providedProtocol.hashCode();
+ }
}
diff --git a/src/main/java/org/java_websocket/protocols/package-info.java b/src/main/java/org/java_websocket/protocols/package-info.java
index 5fbb3ae1d..6f8132b55 100644
--- a/src/main/java/org/java_websocket/protocols/package-info.java
+++ b/src/main/java/org/java_websocket/protocols/package-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -24,6 +24,7 @@
*/
/**
- * This package encapsulates all interfaces and implementations in relation with the WebSocket Sec-WebSocket-Protocol.
+ * This package encapsulates all interfaces and implementations in relation with the WebSocket
+ * Sec-WebSocket-Protocol.
*/
package org.java_websocket.protocols;
\ No newline at end of file
diff --git a/src/main/java/org/java_websocket/server/CustomSSLWebSocketServerFactory.java b/src/main/java/org/java_websocket/server/CustomSSLWebSocketServerFactory.java
index 26783842f..1246b22d9 100644
--- a/src/main/java/org/java_websocket/server/CustomSSLWebSocketServerFactory.java
+++ b/src/main/java/org/java_websocket/server/CustomSSLWebSocketServerFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -25,68 +25,76 @@
package org.java_websocket.server;
-import org.java_websocket.SSLSocketChannel2;
-
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLEngine;
import java.io.IOException;
import java.nio.channels.ByteChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import org.java_websocket.SSLSocketChannel2;
/**
* WebSocketFactory that can be configured to only support specific protocols and cipher suites.
*/
public class CustomSSLWebSocketServerFactory extends DefaultSSLWebSocketServerFactory {
- /**
- * The enabled protocols saved as a String array
- */
- private final String[] enabledProtocols;
+ /**
+ * The enabled protocols saved as a String array
+ */
+ private final String[] enabledProtocols;
- /**
- * The enabled ciphersuites saved as a String array
- */
- private final String[] enabledCiphersuites;
+ /**
+ * The enabled ciphersuites saved as a String array
+ */
+ private final String[] enabledCiphersuites;
- /**
- * New CustomSSLWebSocketServerFactory configured to only support given protocols and given cipher suites.
- *
- * @param sslContext - can not be null
- * @param enabledProtocols - only these protocols are enabled, when null default settings will be used.
- * @param enabledCiphersuites - only these cipher suites are enabled, when null default settings will be used.
- */
- public CustomSSLWebSocketServerFactory(SSLContext sslContext, String[] enabledProtocols, String[] enabledCiphersuites) {
- this(sslContext, Executors.newSingleThreadScheduledExecutor(), enabledProtocols, enabledCiphersuites);
- }
+ /**
+ * New CustomSSLWebSocketServerFactory configured to only support given protocols and given cipher
+ * suites.
+ *
+ * @param sslContext - can not be null
+ * @param enabledProtocols - only these protocols are enabled, when null default
+ * settings will be used.
+ * @param enabledCiphersuites - only these cipher suites are enabled, when null
+ * default settings will be used.
+ */
+ public CustomSSLWebSocketServerFactory(SSLContext sslContext, String[] enabledProtocols,
+ String[] enabledCiphersuites) {
+ this(sslContext, Executors.newSingleThreadScheduledExecutor(), enabledProtocols,
+ enabledCiphersuites);
+ }
- /**
- * New CustomSSLWebSocketServerFactory configured to only support given protocols and given cipher suites.
- *
- * @param sslContext - can not be null
- * @param executerService - can not be null
- * @param enabledProtocols - only these protocols are enabled, when null default settings will be used.
- * @param enabledCiphersuites - only these cipher suites are enabled, when null default settings will be used.
- */
- public CustomSSLWebSocketServerFactory(SSLContext sslContext, ExecutorService executerService, String[] enabledProtocols, String[] enabledCiphersuites) {
- super(sslContext, executerService);
- this.enabledProtocols = enabledProtocols;
- this.enabledCiphersuites = enabledCiphersuites;
- }
+ /**
+ * New CustomSSLWebSocketServerFactory configured to only support given protocols and given cipher
+ * suites.
+ *
+ * @param sslContext - can not be null
+ * @param executerService - can not be null
+ * @param enabledProtocols - only these protocols are enabled, when null default
+ * settings will be used.
+ * @param enabledCiphersuites - only these cipher suites are enabled, when null
+ * default settings will be used.
+ */
+ public CustomSSLWebSocketServerFactory(SSLContext sslContext, ExecutorService executerService,
+ String[] enabledProtocols, String[] enabledCiphersuites) {
+ super(sslContext, executerService);
+ this.enabledProtocols = enabledProtocols;
+ this.enabledCiphersuites = enabledCiphersuites;
+ }
- @Override
- public ByteChannel wrapChannel(SocketChannel channel, SelectionKey key) throws IOException {
- SSLEngine e = sslcontext.createSSLEngine();
- if (enabledProtocols != null) {
- e.setEnabledProtocols(enabledProtocols);
- }
- if (enabledCiphersuites != null) {
- e.setEnabledCipherSuites(enabledCiphersuites);
- }
- e.setUseClientMode(false);
- return new SSLSocketChannel2(channel, e, exec, key);
+ @Override
+ public ByteChannel wrapChannel(SocketChannel channel, SelectionKey key) throws IOException {
+ SSLEngine e = sslcontext.createSSLEngine();
+ if (enabledProtocols != null) {
+ e.setEnabledProtocols(enabledProtocols);
+ }
+ if (enabledCiphersuites != null) {
+ e.setEnabledCipherSuites(enabledCiphersuites);
}
+ e.setUseClientMode(false);
+ return new SSLSocketChannel2(channel, e, exec, key);
+ }
}
\ No newline at end of file
diff --git a/src/main/java/org/java_websocket/server/DefaultSSLWebSocketServerFactory.java b/src/main/java/org/java_websocket/server/DefaultSSLWebSocketServerFactory.java
index 5deddc156..f2732030c 100644
--- a/src/main/java/org/java_websocket/server/DefaultSSLWebSocketServerFactory.java
+++ b/src/main/java/org/java_websocket/server/DefaultSSLWebSocketServerFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -24,6 +24,7 @@
*/
package org.java_websocket.server;
+
import java.io.IOException;
import java.nio.channels.ByteChannel;
import java.nio.channels.SelectionKey;
@@ -33,10 +34,8 @@
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
-
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
-
import org.java_websocket.SSLSocketChannel2;
import org.java_websocket.WebSocketAdapter;
import org.java_websocket.WebSocketImpl;
@@ -44,47 +43,50 @@
import org.java_websocket.drafts.Draft;
public class DefaultSSLWebSocketServerFactory implements WebSocketServerFactory {
- protected SSLContext sslcontext;
- protected ExecutorService exec;
- public DefaultSSLWebSocketServerFactory( SSLContext sslContext ) {
- this( sslContext, Executors.newSingleThreadScheduledExecutor() );
- }
+ protected SSLContext sslcontext;
+ protected ExecutorService exec;
+
+ public DefaultSSLWebSocketServerFactory(SSLContext sslContext) {
+ this(sslContext, Executors.newSingleThreadScheduledExecutor());
+ }
+
+ public DefaultSSLWebSocketServerFactory(SSLContext sslContext, ExecutorService exec) {
+ if (sslContext == null || exec == null) {
+ throw new IllegalArgumentException();
+ }
+ this.sslcontext = sslContext;
+ this.exec = exec;
+ }
- public DefaultSSLWebSocketServerFactory( SSLContext sslContext , ExecutorService exec ) {
- if( sslContext == null || exec == null )
- throw new IllegalArgumentException();
- this.sslcontext = sslContext;
- this.exec = exec;
- }
+ @Override
+ public ByteChannel wrapChannel(SocketChannel channel, SelectionKey key) throws IOException {
+ SSLEngine e = sslcontext.createSSLEngine();
+ /*
+ * See https://github.com/TooTallNate/Java-WebSocket/issues/466
+ *
+ * We remove TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 from the enabled ciphers since it is just available when you patch your java installation directly.
+ * E.g. firefox requests this cipher and this causes some dcs/instable connections
+ */
+ List ciphers = new ArrayList<>(Arrays.asList(e.getEnabledCipherSuites()));
+ ciphers.remove("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256");
+ e.setEnabledCipherSuites(ciphers.toArray(new String[ciphers.size()]));
+ e.setUseClientMode(false);
+ return new SSLSocketChannel2(channel, e, exec, key);
+ }
- @Override
- public ByteChannel wrapChannel( SocketChannel channel, SelectionKey key ) throws IOException {
- SSLEngine e = sslcontext.createSSLEngine();
- /*
- * See https://github.com/TooTallNate/Java-WebSocket/issues/466
- *
- * We remove TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 from the enabled ciphers since it is just available when you patch your java installation directly.
- * E.g. firefox requests this cipher and this causes some dcs/instable connections
- */
- List ciphers = new ArrayList( Arrays.asList(e.getEnabledCipherSuites()));
- ciphers.remove("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256");
- e.setEnabledCipherSuites( ciphers.toArray( new String[ciphers.size()] ) );
- e.setUseClientMode( false );
- return new SSLSocketChannel2( channel, e, exec, key );
- }
+ @Override
+ public WebSocketImpl createWebSocket(WebSocketAdapter a, Draft d) {
+ return new WebSocketImpl(a, d);
+ }
- @Override
- public WebSocketImpl createWebSocket( WebSocketAdapter a, Draft d) {
- return new WebSocketImpl( a, d );
- }
+ @Override
+ public WebSocketImpl createWebSocket(WebSocketAdapter a, List d) {
+ return new WebSocketImpl(a, d);
+ }
- @Override
- public WebSocketImpl createWebSocket( WebSocketAdapter a, List d) {
- return new WebSocketImpl( a, d );
- }
- @Override
- public void close() {
- exec.shutdown();
- }
+ @Override
+ public void close() {
+ exec.shutdown();
+ }
}
\ No newline at end of file
diff --git a/src/main/java/org/java_websocket/server/DefaultWebSocketServerFactory.java b/src/main/java/org/java_websocket/server/DefaultWebSocketServerFactory.java
index c8d9d324e..80527e8e7 100644
--- a/src/main/java/org/java_websocket/server/DefaultWebSocketServerFactory.java
+++ b/src/main/java/org/java_websocket/server/DefaultWebSocketServerFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -28,26 +28,30 @@
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.List;
-
import org.java_websocket.WebSocketAdapter;
import org.java_websocket.WebSocketImpl;
-import org.java_websocket.drafts.Draft;
import org.java_websocket.WebSocketServerFactory;
+import org.java_websocket.drafts.Draft;
public class DefaultWebSocketServerFactory implements WebSocketServerFactory {
- @Override
- public WebSocketImpl createWebSocket( WebSocketAdapter a, Draft d) {
- return new WebSocketImpl( a, d );
- }
- @Override
- public WebSocketImpl createWebSocket( WebSocketAdapter a, List d) {
- return new WebSocketImpl( a, d );
- }
- @Override
- public SocketChannel wrapChannel( SocketChannel channel, SelectionKey key ) {
- return channel;
- }
- @Override
- public void close() {
- }
+
+ @Override
+ public WebSocketImpl createWebSocket(WebSocketAdapter a, Draft d) {
+ return new WebSocketImpl(a, d);
+ }
+
+ @Override
+ public WebSocketImpl createWebSocket(WebSocketAdapter a, List d) {
+ return new WebSocketImpl(a, d);
+ }
+
+ @Override
+ public SocketChannel wrapChannel(SocketChannel channel, SelectionKey key) {
+ return channel;
+ }
+
+ @Override
+ public void close() {
+ //Nothing to do for a normal ws factory
+ }
}
\ No newline at end of file
diff --git a/src/main/java/org/java_websocket/server/SSLParametersWebSocketServerFactory.java b/src/main/java/org/java_websocket/server/SSLParametersWebSocketServerFactory.java
new file mode 100644
index 000000000..d7685bfd8
--- /dev/null
+++ b/src/main/java/org/java_websocket/server/SSLParametersWebSocketServerFactory.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2010-2020 Nathan Rajlich
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package org.java_websocket.server;
+
+import java.io.IOException;
+import java.nio.channels.ByteChannel;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.SocketChannel;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLParameters;
+import org.java_websocket.SSLSocketChannel2;
+
+/**
+ * WebSocketFactory that can be configured to only support specific protocols and cipher suites.
+ */
+public class SSLParametersWebSocketServerFactory extends DefaultSSLWebSocketServerFactory {
+
+ private final SSLParameters sslParameters;
+
+ /**
+ * New CustomSSLWebSocketServerFactory configured to only support given protocols and given cipher
+ * suites.
+ *
+ * @param sslContext - can not be null
+ * @param sslParameters - can not be null
+ */
+ public SSLParametersWebSocketServerFactory(SSLContext sslContext, SSLParameters sslParameters) {
+ this(sslContext, Executors.newSingleThreadScheduledExecutor(), sslParameters);
+ }
+
+ /**
+ * New CustomSSLWebSocketServerFactory configured to only support given protocols and given cipher
+ * suites.
+ *
+ * @param sslContext - can not be null
+ * @param executerService - can not be null
+ * @param sslParameters - can not be null
+ */
+ public SSLParametersWebSocketServerFactory(SSLContext sslContext, ExecutorService executerService,
+ SSLParameters sslParameters) {
+ super(sslContext, executerService);
+ if (sslParameters == null) {
+ throw new IllegalArgumentException();
+ }
+ this.sslParameters = sslParameters;
+ }
+
+ @Override
+ public ByteChannel wrapChannel(SocketChannel channel, SelectionKey key) throws IOException {
+ SSLEngine e = sslcontext.createSSLEngine();
+ e.setUseClientMode(false);
+ e.setSSLParameters(sslParameters);
+ return new SSLSocketChannel2(channel, e, exec, key);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/java_websocket/server/WebSocketServer.java b/src/main/java/org/java_websocket/server/WebSocketServer.java
index 39c944c2b..8d11bcf48 100644
--- a/src/main/java/org/java_websocket/server/WebSocketServer.java
+++ b/src/main/java/org/java_websocket/server/WebSocketServer.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2018 Nathan Rajlich
+ * Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -29,6 +29,7 @@
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
+import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.ClosedByInterruptException;
@@ -37,905 +38,1136 @@
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
-
-import org.java_websocket.*;
+import org.java_websocket.AbstractWebSocket;
+import org.java_websocket.SocketChannelIOHelper;
+import org.java_websocket.WebSocket;
+import org.java_websocket.WebSocketFactory;
+import org.java_websocket.WebSocketImpl;
+import org.java_websocket.WebSocketServerFactory;
+import org.java_websocket.WrappedByteChannel;
import org.java_websocket.drafts.Draft;
-import org.java_websocket.exceptions.InvalidDataException;
import org.java_websocket.exceptions.WebsocketNotConnectedException;
+import org.java_websocket.exceptions.WrappedIOException;
import org.java_websocket.framing.CloseFrame;
import org.java_websocket.framing.Framedata;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.handshake.Handshakedata;
-import org.java_websocket.handshake.ServerHandshakeBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
- * WebSocketServer is an abstract class that only takes care of the
- * HTTP handshake portion of WebSockets. It's up to a subclass to add
- * functionality/purpose to the server.
- *
+ * WebSocketServer is an abstract class that only takes care of the
+ * HTTP handshake portion of WebSockets. It's up to a subclass to add functionality/purpose to the
+ * server.
*/
public abstract class WebSocketServer extends AbstractWebSocket implements Runnable {
- public static int DECODERS = Runtime.getRuntime().availableProcessors();
-
- /**
- * Holds the list of active WebSocket connections. "Active" means WebSocket
- * handshake is complete and socket can be written to, or read from.
- */
- private final Collection connections;
- /**
- * The port number that this WebSocket server should listen on. Default is
- * WebSocket.DEFAULT_PORT.
- */
- private final InetSocketAddress address;
- /**
- * The socket channel for this WebSocket server.
- */
- private ServerSocketChannel server;
- /**
- * The 'Selector' used to get event keys from the underlying socket.
- */
- private Selector selector;
- /**
- * The Draft of the WebSocket protocol the Server is adhering to.
- */
- private List drafts;
-
- private Thread selectorthread;
-
- private final AtomicBoolean isclosed = new AtomicBoolean( false );
-
- protected List decoders;
-
- private List iqueue;
- private BlockingQueue buffers;
- private int queueinvokes = 0;
- private final AtomicInteger queuesize = new AtomicInteger( 0 );
-
- private WebSocketServerFactory wsf = new DefaultWebSocketServerFactory();
-
- /**
- * Creates a WebSocketServer that will attempt to
- * listen on port WebSocket.DEFAULT_PORT.
- *
- * @see #WebSocketServer(InetSocketAddress, int, List, Collection) more details here
- */
- public WebSocketServer() {
- this( new InetSocketAddress( WebSocket.DEFAULT_PORT ), DECODERS, null );
- }
-
- /**
- * Creates a WebSocketServer that will attempt to bind/listen on the given address.
- *
- * @see #WebSocketServer(InetSocketAddress, int, List, Collection) more details here
- * @param address The address to listen to
- */
- public WebSocketServer( InetSocketAddress address ) {
- this( address, DECODERS, null );
- }
-
- /**
- * @see #WebSocketServer(InetSocketAddress, int, List, Collection) more details here
- * @param address
- * The address (host:port) this server should listen on.
- * @param decodercount
- * The number of {@link WebSocketWorker}s that will be used to process the incoming network data. By default this will be Runtime.getRuntime().availableProcessors()
- */
- public WebSocketServer( InetSocketAddress address , int decodercount ) {
- this( address, decodercount, null );
- }
-
- /**
- * @see #WebSocketServer(InetSocketAddress, int, List, Collection) more details here
- *
- * @param address
- * The address (host:port) this server should listen on.
- * @param drafts
- * The versions of the WebSocket protocol that this server
- * instance should comply to. Clients that use an other protocol version will be rejected.
- *
- */
- public WebSocketServer( InetSocketAddress address , List drafts ) {
- this( address, DECODERS, drafts );
- }
-
- /**
- * @see #WebSocketServer(InetSocketAddress, int, List, Collection) more details here
- *
- * @param address
- * The address (host:port) this server should listen on.
- * @param decodercount
- * The number of {@link WebSocketWorker}s that will be used to process the incoming network data. By default this will be Runtime.getRuntime().availableProcessors()
- * @param drafts
- * The versions of the WebSocket protocol that this server
- * instance should comply to. Clients that use an other protocol version will be rejected.
-
- */
- public WebSocketServer( InetSocketAddress address , int decodercount , List drafts ) {
- this( address, decodercount, drafts, new HashSet() );
- }
-
- /**
- * Creates a WebSocketServer that will attempt to bind/listen on the given address,
- * and comply with Draft version draft.
- *
- * @param address
- * The address (host:port) this server should listen on.
- * @param decodercount
- * The number of {@link WebSocketWorker}s that will be used to process the incoming network data. By default this will be Runtime.getRuntime().availableProcessors()
- * @param drafts
- * The versions of the WebSocket protocol that this server
- * instance should comply to. Clients that use an other protocol version will be rejected.
- *
- * @param connectionscontainer
- * Allows to specify a collection that will be used to store the websockets in.
- * If you plan to often iterate through the currently connected websockets you may want to use a collection that does not require synchronization like a {@link CopyOnWriteArraySet}. In that case make sure that you overload {@link #removeConnection(WebSocket)} and {@link #addConnection(WebSocket)}.
- * By default a {@link HashSet} will be used.
- *
- * @see #removeConnection(WebSocket) for more control over syncronized operation
- * @see more about drafts
- */
- public WebSocketServer( InetSocketAddress address , int decodercount , List drafts , Collection connectionscontainer ) {
- if( address == null || decodercount < 1 || connectionscontainer == null ) {
- throw new IllegalArgumentException( "address and connectionscontainer must not be null and you need at least 1 decoder" );
- }
-
- if( drafts == null )
- this.drafts = Collections.emptyList();
- else
- this.drafts = drafts;
-
- this.address = address;
- this.connections = connectionscontainer;
- setTcpNoDelay(false);
- setReuseAddr(false);
- iqueue = new LinkedList();
-
- decoders = new ArrayList( decodercount );
- buffers = new LinkedBlockingQueue();
- for( int i = 0 ; i < decodercount ; i++ ) {
- WebSocketWorker ex = new WebSocketWorker();
- decoders.add( ex );
- ex.start();
- }
- }
-
-
- /**
- * Starts the server selectorthread that binds to the currently set port number and
- * listeners for WebSocket connection requests. Creates a fixed thread pool with the size {@link WebSocketServer#DECODERS}
- * May only be called once.
- *
- * Alternatively you can call {@link WebSocketServer#run()} directly.
- *
- * @throws IllegalStateException Starting an instance again
- */
- public void start() {
- if( selectorthread != null )
- throw new IllegalStateException( getClass().getName() + " can only be started once." );
- new Thread( this ).start();
- }
-
- /**
- * Closes all connected clients sockets, then closes the underlying
- * ServerSocketChannel, effectively killing the server socket selectorthread,
- * freeing the port the server was bound to and stops all internal workerthreads.
- *
- * If this method is called before the server is started it will never start.
- *
- * @param timeout
- * Specifies how many milliseconds the overall close handshaking may take altogether before the connections are closed without proper close handshaking.
- *
- * @throws InterruptedException Interrupt
- */
- public void stop( int timeout ) throws InterruptedException {
- if( !isclosed.compareAndSet( false, true ) ) { // this also makes sure that no further connections will be added to this.connections
- return;
- }
-
- List socketsToClose;
-
- // copy the connections in a list (prevent callback deadlocks)
- synchronized ( connections ) {
- socketsToClose = new ArrayList( connections );
- }
-
- for( WebSocket ws : socketsToClose ) {
- ws.close( CloseFrame.GOING_AWAY );
- }
-
- wsf.close();
-
- synchronized ( this ) {
- if( selectorthread != null && selector != null) {
- selector.wakeup();
- selectorthread.join( timeout );
- }
- }
- }
- public void stop() throws IOException , InterruptedException {
- stop( 0 );
- }
-
- /**
- * PLEASE use the method getConnections() in the future!
- *
- * Returns a WebSocket[] of currently connected clients.
- * Its iterators will be failfast and its not judicious
- * to modify it.
- *
- * @return The currently connected clients.
- *
- */
- @Deprecated
- public Collection connections() {
- return getConnections();
- }
-
- /**
- * Returns all currently connected clients.
- * This collection does not allow any modification e.g. removing a client.
- *
- * @return A unmodifiable collection of all currently connected clients
- * @since 1.3.8
- */
- public Collection getConnections() {
- return Collections.unmodifiableCollection( new ArrayList(connections) );
- }
-
- public InetSocketAddress getAddress() {
- return this.address;
- }
-
- /**
- * Gets the port number that this server listens on.
- *
- * @return The port number.
- */
- public int getPort() {
- int port = getAddress().getPort();
- if( port == 0 && server != null ) {
- port = server.socket().getLocalPort();
- }
- return port;
- }
-
- public List getDraft() {
- return Collections.unmodifiableList( drafts );
- }
-
- // Runnable IMPLEMENTATION /////////////////////////////////////////////////
- public void run() {
- synchronized ( this ) {
- if( selectorthread != null )
- throw new IllegalStateException( getClass().getName() + " can only be started once." );
- selectorthread = Thread.currentThread();
- if( isclosed.get() ) {
- return;
- }
- }
- selectorthread.setName( "WebSocketSelector-" + selectorthread.getId() );
- try {
- server = ServerSocketChannel.open();
- server.configureBlocking( false );
- ServerSocket socket = server.socket();
- socket.setReceiveBufferSize( WebSocketImpl.RCVBUF );
- socket.setReuseAddress( isReuseAddr() );
- socket.bind( address );
- selector = Selector.open();
- server.register( selector, server.validOps() );
- startConnectionLostTimer();
- onStart();
- } catch ( IOException ex ) {
- handleFatal( null, ex );
- return;
- }
- try {
- int iShutdownCount = 5;
- int selectTimeout = 0;
- while ( !selectorthread.isInterrupted() && iShutdownCount != 0) {
- SelectionKey key = null;
- WebSocketImpl conn = null;
- try {
- if (isclosed.get()) {
- selectTimeout = 5;
- }
- int keyCount = selector.select( selectTimeout );
- if (keyCount == 0 && isclosed.get()) {
- iShutdownCount--;
- }
- Set keys = selector.selectedKeys();
- Iterator i = keys.iterator();
-
- while ( i.hasNext() ) {
- key = i.next();
- conn = null;
-
- if( !key.isValid() ) {
- // Object o = key.attachment();
- continue;
- }
-
- if( key.isAcceptable() ) {
- if( !onConnect( key ) ) {
- key.cancel();
- continue;
- }
-
- SocketChannel channel = server.accept();
- if(channel==null){
- continue;
- }
- channel.configureBlocking( false );
- Socket socket = channel.socket();
- socket.setTcpNoDelay( isTcpNoDelay() );
- socket.setKeepAlive( true );
- WebSocketImpl w = wsf.createWebSocket( this, drafts );
- w.key = channel.register( selector, SelectionKey.OP_READ, w );
- try {
- w.channel = wsf.wrapChannel( channel, w.key );
- i.remove();
- allocateBuffers( w );
- continue;
- } catch (IOException ex) {
- if( w.key != null )
- w.key.cancel();
-
- handleIOException( w.key, null, ex );
- }
- continue;
- }
-
- if( key.isReadable() ) {
- conn = (WebSocketImpl) key.attachment();
- ByteBuffer buf = takeBuffer();
- if(conn.channel == null){
- if( key != null )
- key.cancel();
-
- handleIOException( key, conn, new IOException() );
- continue;
- }
- try {
- if( SocketChannelIOHelper.read( buf, conn, conn.channel ) ) {
- if( buf.hasRemaining() ) {
- conn.inQueue.put( buf );
- queue( conn );
- i.remove();
- if( conn.channel instanceof WrappedByteChannel ) {
- if( ( (WrappedByteChannel) conn.channel ).isNeedRead() ) {
- iqueue.add( conn );
- }
- }
- } else
- pushBuffer( buf );
- } else {
- pushBuffer( buf );
- }
- } catch ( IOException e ) {
- pushBuffer( buf );
- throw e;
- }
- }
- if( key.isWritable() ) {
- conn = (WebSocketImpl) key.attachment();
- if( SocketChannelIOHelper.batch( conn, conn.channel ) ) {
- if( key.isValid() )
- key.interestOps( SelectionKey.OP_READ );
- }
- }
- }
- while ( !iqueue.isEmpty() ) {
- conn = iqueue.remove( 0 );
- WrappedByteChannel c = ( (WrappedByteChannel) conn.channel );
- ByteBuffer buf = takeBuffer();
- try {
- if( SocketChannelIOHelper.readMore( buf, conn, c ) )
- iqueue.add( conn );
- if( buf.hasRemaining() ) {
- conn.inQueue.put( buf );
- queue( conn );
- } else {
- pushBuffer( buf );
- }
- } catch ( IOException e ) {
- pushBuffer( buf );
- throw e;
- }
-
- }
- } catch ( CancelledKeyException e ) {
- // an other thread may cancel the key
- } catch ( ClosedByInterruptException e ) {
- return; // do the same stuff as when InterruptedException is thrown
- } catch ( IOException ex ) {
- if( key != null )
- key.cancel();
- handleIOException( key, conn, ex );
- } catch ( InterruptedException e ) {
- return;// FIXME controlled shutdown (e.g. take care of buffermanagement)
- }
- }
-
- } catch ( RuntimeException e ) {
- // should hopefully never occur
- handleFatal( null, e );
- } finally {
- stopConnectionLostTimer();
- if( decoders != null ) {
- for( WebSocketWorker w : decoders ) {
- w.interrupt();
- }
- }
- if( selector != null ) {
- try {
- selector.close();
- } catch ( IOException e ) {
- onError( null, e );
- }
- }
- if( server != null ) {
- try {
- server.close();
- } catch ( IOException e ) {
- onError( null, e );
- }
- }
- }
- }
- protected void allocateBuffers( WebSocket c ) throws InterruptedException {
- if( queuesize.get() >= 2 * decoders.size() + 1 ) {
- return;
- }
- queuesize.incrementAndGet();
- buffers.put( createBuffer() );
- }
-
- protected void releaseBuffers( WebSocket c ) throws InterruptedException {
- // queuesize.decrementAndGet();
- // takeBuffer();
- }
-
- public ByteBuffer createBuffer() {
- return ByteBuffer.allocate( WebSocketImpl.RCVBUF );
- }
-
- protected void queue( WebSocketImpl ws ) throws InterruptedException {
- if( ws.workerThread == null ) {
- ws.workerThread = decoders.get( queueinvokes % decoders.size() );
- queueinvokes++;
- }
- ws.workerThread.put( ws );
- }
-
- private ByteBuffer takeBuffer() throws InterruptedException {
- return buffers.take();
- }
-
- private void pushBuffer( ByteBuffer buf ) throws InterruptedException {
- if( buffers.size() > queuesize.intValue() )
- return;
- buffers.put( buf );
- }
-
- private void handleIOException( SelectionKey key, WebSocket conn, IOException ex ) {
- // onWebsocketError( conn, ex );// conn may be null here
- if( conn != null ) {
- conn.closeConnection( CloseFrame.ABNORMAL_CLOSE, ex.getMessage() );
- } else if( key != null ) {
- SelectableChannel channel = key.channel();
- if( channel != null && channel.isOpen() ) { // this could be the case if the IOException ex is a SSLException
- try {
- channel.close();
- } catch ( IOException e ) {
- // there is nothing that must be done here
- }
- if( WebSocketImpl.DEBUG )
- System.out.println("Connection closed because of " + ex);
- }
- }
- }
-
- private void handleFatal( WebSocket conn, Exception e ) {
- onError( conn, e );
- //Shutting down WebSocketWorkers, see #222
- if( decoders != null ) {
- for( WebSocketWorker w : decoders ) {
- w.interrupt();
- }
- }
- if (selectorthread != null) {
- selectorthread.interrupt();
- }
- try {
- stop();
- } catch ( IOException e1 ) {
- onError( null, e1 );
- } catch ( InterruptedException e1 ) {
- Thread.currentThread().interrupt();
- onError( null, e1 );
- }
- }
-
-
- @Override
- public final void onWebsocketMessage( WebSocket conn, String message ) {
- onMessage( conn, message );
- }
-
- @Override
- @Deprecated
- public/*final*/void onWebsocketMessageFragment( WebSocket conn, Framedata frame ) {// onFragment should be overloaded instead
- onFragment( conn, frame );
- }
-
- @Override
- public final void onWebsocketMessage( WebSocket conn, ByteBuffer blob ) {
- onMessage( conn, blob );
- }
-
- @Override
- public final void onWebsocketOpen( WebSocket conn, Handshakedata handshake ) {
- if( addConnection( conn ) ) {
- onOpen( conn, (ClientHandshake) handshake );
- }
- }
-
- @Override
- public final void onWebsocketClose( WebSocket conn, int code, String reason, boolean remote ) {
- selector.wakeup();
- try {
- if( removeConnection( conn ) ) {
- onClose( conn, code, reason, remote );
- }
- } finally {
- try {
- releaseBuffers( conn );
- } catch ( InterruptedException e ) {
- Thread.currentThread().interrupt();
- }
- }
-
- }
-
- /**
- * This method performs remove operations on the connection and therefore also gives control over whether the operation shall be synchronized
- *
- * {@link #WebSocketServer(InetSocketAddress, int, List, Collection)} allows to specify a collection which will be used to store current connections in.
- * Depending on the type on the connection, modifications of that collection may have to be synchronized.
- * @param ws The Webscoket connection which should be removed
- * @return Removing connection successful
- */
- protected boolean removeConnection( WebSocket ws ) {
- boolean removed = false;
- synchronized ( connections ) {
- if (this.connections.contains( ws )) {
- removed = this.connections.remove( ws );
- } else {
- //Don't throw an assert error if the ws is not in the list. e.g. when the other endpoint did not send any handshake. see #512
- if (WebSocketImpl.DEBUG) {
- System.out.println("Removing connection which is not in the connections collection! Possible no handshake recieved! " + ws);
- }
- }
- }
- if( isclosed.get() && connections.size() == 0 ) {
- selectorthread.interrupt();
- }
- return removed;
- }
- @Override
- public ServerHandshakeBuilder onWebsocketHandshakeReceivedAsServer( WebSocket conn, Draft draft, ClientHandshake request ) throws InvalidDataException {
- return super.onWebsocketHandshakeReceivedAsServer( conn, draft, request );
- }
-
- /**
- * @see #removeConnection(WebSocket)
- * @param ws the Webscoket connection which should be added
- * @return Adding connection successful
- */
- protected boolean addConnection( WebSocket ws ) {
- if( !isclosed.get() ) {
- synchronized ( connections ) {
- boolean succ = this.connections.add( ws );
- assert ( succ );
- return succ;
- }
- } else {
- // This case will happen when a new connection gets ready while the server is already stopping.
- ws.close( CloseFrame.GOING_AWAY );
- return true;// for consistency sake we will make sure that both onOpen will be called
- }
- }
-
- @Override
- public final void onWebsocketError( WebSocket conn, Exception ex ) {
- onError( conn, ex );
- }
-
- @Override
- public final void onWriteDemand( WebSocket w ) {
- WebSocketImpl conn = (WebSocketImpl) w;
- try {
- conn.key.interestOps( SelectionKey.OP_READ | SelectionKey.OP_WRITE );
- } catch ( CancelledKeyException e ) {
- // the thread which cancels key is responsible for possible cleanup
- conn.outQueue.clear();
- }
- selector.wakeup();
- }
-
- @Override
- public void onWebsocketCloseInitiated( WebSocket conn, int code, String reason ) {
- onCloseInitiated( conn, code, reason );
- }
-
- @Override
- public void onWebsocketClosing( WebSocket conn, int code, String reason, boolean remote ) {
- onClosing( conn, code, reason, remote );
-
- }
-
- public void onCloseInitiated( WebSocket conn, int code, String reason ) {
- }
-
- public void onClosing( WebSocket conn, int code, String reason, boolean remote ) {
-
- }
-
- public final void setWebSocketFactory( WebSocketServerFactory wsf ) {
- this.wsf = wsf;
- }
-
- public final WebSocketFactory getWebSocketFactory() {
- return wsf;
- }
-
- /**
- * Returns whether a new connection shall be accepted or not.
- * Therefore method is well suited to implement some kind of connection limitation.
- *
- * @see #onOpen(WebSocket, ClientHandshake)
- * @see #onWebsocketHandshakeReceivedAsServer(WebSocket, Draft, ClientHandshake)
- * @param key the SelectionKey for the new connection
- * @return Can this new connection be accepted
- **/
- protected boolean onConnect( SelectionKey key ) {
- //FIXME
- return true;
- }
-
- /**
- * Getter to return the socket used by this specific connection
- * @param conn The specific connection
- * @return The socket used by this connection
- */
- private Socket getSocket( WebSocket conn ) {
- WebSocketImpl impl = (WebSocketImpl) conn;
- return ( (SocketChannel) impl.key.channel() ).socket();
- }
-
- @Override
- public InetSocketAddress getLocalSocketAddress( WebSocket conn ) {
- return (InetSocketAddress) getSocket( conn ).getLocalSocketAddress();
- }
-
- @Override
- public InetSocketAddress getRemoteSocketAddress( WebSocket conn ) {
- return (InetSocketAddress) getSocket( conn ).getRemoteSocketAddress();
- }
-
- /** Called after an opening handshake has been performed and the given websocket is ready to be written on.
- * @param conn The WebSocket instance this event is occuring on.
- * @param handshake The handshake of the websocket instance
- */
- public abstract void onOpen( WebSocket conn, ClientHandshake handshake );
- /**
- * Called after the websocket connection has been closed.
- *
- * @param conn The WebSocket instance this event is occuring on.
- * @param code
- * The codes can be looked up here: {@link CloseFrame}
- * @param reason
- * Additional information string
- * @param remote
- * Returns whether or not the closing of the connection was initiated by the remote host.
- **/
- public abstract void onClose( WebSocket conn, int code, String reason, boolean remote );
- /**
- * Callback for string messages received from the remote host
- *
- * @see #onMessage(WebSocket, ByteBuffer)
- * @param conn The WebSocket instance this event is occuring on.
- * @param message The UTF-8 decoded message that was received.
- **/
- public abstract void onMessage( WebSocket conn, String message );
- /**
- * Called when errors occurs. If an error causes the websocket connection to fail {@link #onClose(WebSocket, int, String, boolean)} will be called additionally.
- * This method will be called primarily because of IO or protocol errors.
- * If the given exception is an RuntimeException that probably means that you encountered a bug.
- *
- * @param conn Can be null if there error does not belong to one specific websocket. For example if the servers port could not be bound.
- * @param ex The exception causing this error
- **/
- public abstract void onError( WebSocket conn, Exception ex );
-
- /**
- * Called when the server started up successfully.
- *
- * If any error occured, onError is called instead.
- */
- public abstract void onStart();
-
- /**
- * Callback for binary messages received from the remote host
- *
- * @see #onMessage(WebSocket, ByteBuffer)
- *
- * @param conn
- * The WebSocket instance this event is occurring on.
- * @param message
- * The binary message that was received.
- **/
- public void onMessage( WebSocket conn, ByteBuffer message ) {
- }
-
- /**
- * Callback for fragmented frames
- * @see WebSocket#sendFragmentedFrame(org.java_websocket.framing.Framedata.Opcode, ByteBuffer, boolean)
- * @param conn
- * The WebSocket instance this event is occurring on.
- * @param fragment The fragmented frame
- */
- @Deprecated
- public void onFragment( WebSocket conn, Framedata fragment ) {
- }
-
- /**
- * Send a text to all connected endpoints
- * @param text the text to send to the endpoints
- */
- public void broadcast(String text) {
- broadcast( text, connections );
- }
-
- /**
- * Send a byte array to all connected endpoints
- * @param data the data to send to the endpoints
- */
- public void broadcast(byte[] data) {
- broadcast( data, connections );
- }
-
- /**
- * Send a ByteBuffer to all connected endpoints
- * @param data the data to send to the endpoints
- */
- public void broadcast(ByteBuffer data) {
- broadcast(data, connections);
- }
-
- /**
- * Send a byte array to a specific collection of websocket connections
- * @param data the data to send to the endpoints
- * @param clients a collection of endpoints to whom the text has to be send
- */
- public void broadcast(byte[] data, Collection clients) {
- if (data == null || clients == null) {
- throw new IllegalArgumentException();
- }
- broadcast(ByteBuffer.wrap(data), clients);
- }
-
- /**
- * Send a ByteBuffer to a specific collection of websocket connections
- * @param data the data to send to the endpoints
- * @param clients a collection of endpoints to whom the text has to be send
- */
- public void broadcast(ByteBuffer data, Collection clients) {
- if (data == null || clients == null) {
- throw new IllegalArgumentException();
- }
- Map> draftFrames = new HashMap>();
- synchronized( clients ) {
- for( WebSocket client : clients ) {
- if( client != null ) {
- Draft draft = client.getDraft();
- if( !draftFrames.containsKey( draft ) ) {
- List frames = draft.createFrames( data, false );
- draftFrames.put( draft, frames );
- }
- try {
- client.sendFrame( draftFrames.get( draft ) );
- } catch ( WebsocketNotConnectedException e ) {
- //Ignore this exception in this case
- }
- }
- }
- }
- }
-
- /**
- * Send a text to a specific collection of websocket connections
- * @param text the text to send to the endpoints
- * @param clients a collection of endpoints to whom the text has to be send
- */
- public void broadcast(String text, Collection clients) {
- if (text == null || clients == null) {
- throw new IllegalArgumentException();
- }
- Map> draftFrames = new HashMap>();
- synchronized( clients ) {
- for( WebSocket client : clients ) {
- if( client != null ) {
- Draft draft = client.getDraft();
- if( !draftFrames.containsKey( draft ) ) {
- List frames = draft.createFrames( text, false );
- draftFrames.put( draft, frames );
- }
- try {
- client.sendFrame( draftFrames.get( draft ) );
- } catch ( WebsocketNotConnectedException e ) {
- //Ignore this exception in this case
- }
- }
- }
- }
- }
-
- /**
- * This class is used to process incoming data
- */
- public class WebSocketWorker extends Thread {
-
- private BlockingQueue iqueue;
-
- public WebSocketWorker() {
- iqueue = new LinkedBlockingQueue();
- setName( "WebSocketWorker-" + getId() );
- setUncaughtExceptionHandler( new UncaughtExceptionHandler() {
- @Override
- public void uncaughtException( Thread t, Throwable e ) {
- System.err.print("Uncaught exception in thread \""
- + t.getName() + "\":");
- e.printStackTrace(System.err);
- }
- } );
- }
-
- public void put( WebSocketImpl ws ) throws InterruptedException {
- iqueue.put( ws );
- }
-
- @Override
- public void run() {
- WebSocketImpl ws = null;
- try {
- while ( true ) {
- ByteBuffer buf;
- ws = iqueue.take();
- buf = ws.inQueue.poll();
- assert ( buf != null );
- try {
- ws.decode( buf );
- } catch(Exception e){
- System.err.println("Error while reading from remote connection: " + e);
- e.printStackTrace();
- }
-
- finally {
- pushBuffer( buf );
- }
- }
- } catch ( InterruptedException e ) {
- } catch ( RuntimeException e ) {
- handleFatal( ws, e );
- }
- }
- }
+ private static final int AVAILABLE_PROCESSORS = Runtime.getRuntime().availableProcessors();
+
+ /**
+ * Logger instance
+ *
+ * @since 1.4.0
+ */
+ private final Logger log = LoggerFactory.getLogger(WebSocketServer.class);
+
+ /**
+ * Holds the list of active WebSocket connections. "Active" means WebSocket handshake is complete
+ * and socket can be written to, or read from.
+ */
+ private final Collection connections;
+ /**
+ * The port number that this WebSocket server should listen on. Default is
+ * WebSocketImpl.DEFAULT_PORT.
+ */
+ private final InetSocketAddress address;
+ /**
+ * The socket channel for this WebSocket server.
+ */
+ private ServerSocketChannel server;
+ /**
+ * The 'Selector' used to get event keys from the underlying socket.
+ */
+ private Selector selector;
+ /**
+ * The Draft of the WebSocket protocol the Server is adhering to.
+ */
+ private List drafts;
+
+ private Thread selectorthread;
+
+ private final AtomicBoolean isclosed = new AtomicBoolean(false);
+
+ protected List decoders;
+
+ private List iqueue;
+ private BlockingQueue buffers;
+ private int queueinvokes = 0;
+ private final AtomicInteger queuesize = new AtomicInteger(0);
+
+ private WebSocketServerFactory wsf = new DefaultWebSocketServerFactory();
+
+ /**
+ * Attribute which allows you to configure the socket "backlog" parameter which determines how
+ * many client connections can be queued.
+ *
+ * @since 1.5.0
+ */
+ private int maxPendingConnections = -1;
+
+ /**
+ * Creates a WebSocketServer that will attempt to listen on port WebSocketImpl.DEFAULT_PORT.
+ *
+ * @see #WebSocketServer(InetSocketAddress, int, List, Collection) more details here
+ */
+ public WebSocketServer() {
+ this(new InetSocketAddress(WebSocketImpl.DEFAULT_PORT), AVAILABLE_PROCESSORS, null);
+ }
+
+ /**
+ * Creates a WebSocketServer that will attempt to bind/listen on the given address.
+ *
+ * @param address The address to listen to
+ * @see #WebSocketServer(InetSocketAddress, int, List, Collection) more details here
+ */
+ public WebSocketServer(InetSocketAddress address) {
+ this(address, AVAILABLE_PROCESSORS, null);
+ }
+
+ /**
+ * @param address The address (host:port) this server should listen on.
+ * @param decodercount The number of {@link WebSocketWorker}s that will be used to process the
+ * incoming network data. By default this will be Runtime.getRuntime().availableProcessors()
+ * @see #WebSocketServer(InetSocketAddress, int, List, Collection) more details here
+ */
+ public WebSocketServer(InetSocketAddress address, int decodercount) {
+ this(address, decodercount, null);
+ }
+
+ /**
+ * @param address The address (host:port) this server should listen on.
+ * @param drafts The versions of the WebSocket protocol that this server instance should comply
+ * to. Clients that use an other protocol version will be rejected.
+ * @see #WebSocketServer(InetSocketAddress, int, List, Collection) more details here
+ */
+ public WebSocketServer(InetSocketAddress address, List drafts) {
+ this(address, AVAILABLE_PROCESSORS, drafts);
+ }
+
+ /**
+ * @param address The address (host:port) this server should listen on.
+ * @param decodercount The number of {@link WebSocketWorker}s that will be used to process the
+ * incoming network data. By default this will be Runtime.getRuntime().availableProcessors()
+ * @param drafts The versions of the WebSocket protocol that this server instance should
+ * comply to. Clients that use an other protocol version will be rejected.
+ * @see #WebSocketServer(InetSocketAddress, int, List, Collection) more details here
+ */
+ public WebSocketServer(InetSocketAddress address, int decodercount, List drafts) {
+ this(address, decodercount, drafts, new HashSet());
+ }
+
+ // Small internal helper function to get around limitations of Java constructors.
+ private static InetSocketAddress checkAddressOfExistingChannel(ServerSocketChannel existingChannel) {
+ assert existingChannel.isOpen();
+ SocketAddress addr;
+ try {
+ addr = existingChannel.getLocalAddress();
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Could not get address of channel passed to WebSocketServer, make sure it is bound", e);
+ }
+ if (addr == null) {
+ throw new IllegalArgumentException("Could not get address of channel passed to WebSocketServer, make sure it is bound");
+ }
+ return (InetSocketAddress)addr;
+ }
+
+ /**
+ * @param existingChannel An already open and bound server socket channel, which this server will use.
+ * For example, it can be System.inheritedChannel() to implement socket activation.
+ */
+ public WebSocketServer(ServerSocketChannel existingChannel) {
+ this(checkAddressOfExistingChannel(existingChannel));
+ this.server = existingChannel;
+ }
+
+ /**
+ * Creates a WebSocketServer that will attempt to bind/listen on the given address, and
+ * comply with Draft version draft.
+ *
+ * @param address The address (host:port) this server should listen on.
+ * @param decodercount The number of {@link WebSocketWorker}s that will be used to process
+ * the incoming network data. By default this will be
+ * Runtime.getRuntime().availableProcessors()
+ * @param drafts The versions of the WebSocket protocol that this server instance
+ * should comply to. Clients that use an other protocol version will
+ * be rejected.
+ * @param connectionscontainer Allows to specify a collection that will be used to store the
+ * websockets in.
If you plan to often iterate through the
+ * currently connected websockets you may want to use a collection
+ * that does not require synchronization like a {@link
+ * CopyOnWriteArraySet}. In that case make sure that you overload
+ * {@link #removeConnection(WebSocket)} and {@link
+ * #addConnection(WebSocket)}.
By default a {@link HashSet} will
+ * be used.
+ * @see #removeConnection(WebSocket) for more control over syncronized operation
+ * @see more about
+ * drafts
+ */
+ public WebSocketServer(InetSocketAddress address, int decodercount, List drafts,
+ Collection connectionscontainer) {
+ if (address == null || decodercount < 1 || connectionscontainer == null) {
+ throw new IllegalArgumentException(
+ "address and connectionscontainer must not be null and you need at least 1 decoder");
+ }
+
+ if (drafts == null) {
+ this.drafts = Collections.emptyList();
+ } else {
+ this.drafts = drafts;
+ }
+
+ this.address = address;
+ this.connections = connectionscontainer;
+ setTcpNoDelay(false);
+ setReuseAddr(false);
+ iqueue = new LinkedList<>();
+
+ decoders = new ArrayList<>(decodercount);
+ buffers = new LinkedBlockingQueue<>();
+ for (int i = 0; i < decodercount; i++) {
+ WebSocketWorker ex = new WebSocketWorker();
+ decoders.add(ex);
+ }
+ }
+
+
+ /**
+ * Starts the server selectorthread that binds to the currently set port number and listeners for
+ * WebSocket connection requests. Creates a fixed thread pool with the size {@link
+ * WebSocketServer#AVAILABLE_PROCESSORS}
May only be called once.
+ *
+ * Alternatively you can call {@link WebSocketServer#run()} directly.
+ *
+ * @throws IllegalStateException Starting an instance again
+ */
+ public void start() {
+ if (selectorthread != null) {
+ throw new IllegalStateException(getClass().getName() + " can only be started once.");
+ }
+ Thread t = new Thread(this);
+ t.setDaemon(isDaemon());
+ t.start();
+ }
+
+ public void stop(int timeout) throws InterruptedException {
+ stop(timeout, "");
+ }
+
+ /**
+ * Closes all connected clients sockets, then closes the underlying ServerSocketChannel,
+ * effectively killing the server socket selectorthread, freeing the port the server was bound to
+ * and stops all internal workerthreads.
+ *
+ * If this method is called before the server is started it will never start.
+ *
+ * @param timeout Specifies how many milliseconds the overall close handshaking may take
+ * altogether before the connections are closed without proper close
+ * handshaking.
+ * @param closeMessage Specifies message for remote client
+ * @throws InterruptedException Interrupt
+ */
+ public void stop(int timeout, String closeMessage) throws InterruptedException {
+ if (!isclosed.compareAndSet(false,
+ true)) { // this also makes sure that no further connections will be added to this.connections
+ return;
+ }
+
+ List socketsToClose;
+
+ // copy the connections in a list (prevent callback deadlocks)
+ synchronized (connections) {
+ socketsToClose = new ArrayList<>(connections);
+ }
+
+ for (WebSocket ws : socketsToClose) {
+ ws.close(CloseFrame.GOING_AWAY, closeMessage);
+ }
+
+ wsf.close();
+
+ synchronized (this) {
+ if (selectorthread != null && selector != null) {
+ selector.wakeup();
+ selectorthread.join(timeout);
+ }
+ }
+ }
+
+ public void stop() throws InterruptedException {
+ stop(0);
+ }
+
+ /**
+ * Returns all currently connected clients. This collection does not allow any modification e.g.
+ * removing a client.
+ *
+ * @return A unmodifiable collection of all currently connected clients
+ * @since 1.3.8
+ */
+ public Collection getConnections() {
+ synchronized (connections) {
+ return Collections.unmodifiableCollection(new ArrayList<>(connections));
+ }
+ }
+
+ public InetSocketAddress getAddress() {
+ return this.address;
+ }
+
+ /**
+ * Gets the port number that this server listens on.
+ *
+ * @return The port number.
+ */
+ public int getPort() {
+ int port = getAddress().getPort();
+ if (port == 0 && server != null) {
+ port = server.socket().getLocalPort();
+ }
+ return port;
+ }
+
+ @Override
+ public void setDaemon(boolean daemon) {
+ // pass it to the AbstractWebSocket too, to use it on the connectionLostChecker thread factory
+ super.setDaemon(daemon);
+ // we need to apply this to the decoders as well since they were created during the constructor
+ for (WebSocketWorker w : decoders) {
+ if (w.isAlive()) {
+ throw new IllegalStateException("Cannot call setDaemon after server is already started!");
+ } else {
+ w.setDaemon(daemon);
+ }
+ }
+ }
+
+ /**
+ * Get the list of active drafts
+ *
+ * @return the available drafts for this server
+ */
+ public List getDraft() {
+ return Collections.unmodifiableList(drafts);
+ }
+
+ /**
+ * Set the requested maximum number of pending connections on the socket. The exact semantics are
+ * implementation specific. The value provided should be greater than 0. If it is less than or
+ * equal to 0, then an implementation specific default will be used. This option will be passed as
+ * "backlog" parameter to {@link ServerSocket#bind(SocketAddress, int)}
+ *
+ * @since 1.5.0
+ * @param numberOfConnections the new number of allowed pending connections
+ */
+ public void setMaxPendingConnections(int numberOfConnections) {
+ maxPendingConnections = numberOfConnections;
+ }
+
+ /**
+ * Returns the currently configured maximum number of pending connections.
+ *
+ * @see #setMaxPendingConnections(int)
+ * @since 1.5.0
+ * @return the maximum number of pending connections
+ */
+ public int getMaxPendingConnections() {
+ return maxPendingConnections;
+ }
+
+ // Runnable IMPLEMENTATION /////////////////////////////////////////////////
+ public void run() {
+ if (!doEnsureSingleThread()) {
+ return;
+ }
+ if (!doSetupSelectorAndServerThread()) {
+ return;
+ }
+ try {
+ int shutdownCount = 5;
+ int selectTimeout = 0;
+ while (!selectorthread.isInterrupted() && shutdownCount != 0) {
+ SelectionKey key = null;
+ try {
+ if (isclosed.get()) {
+ selectTimeout = 5;
+ }
+ int keyCount = selector.select(selectTimeout);
+ if (keyCount == 0 && isclosed.get()) {
+ shutdownCount--;
+ }
+ Set keys = selector.selectedKeys();
+ Iterator i = keys.iterator();
+
+ while (i.hasNext()) {
+ key = i.next();
+
+ if (!key.isValid()) {
+ continue;
+ }
+
+ if (key.isAcceptable()) {
+ doAccept(key, i);
+ continue;
+ }
+
+ if (key.isReadable() && !doRead(key, i)) {
+ continue;
+ }
+
+ if (key.isWritable()) {
+ doWrite(key);
+ }
+ }
+ doAdditionalRead();
+ } catch (CancelledKeyException e) {
+ // an other thread may cancel the key
+ } catch (ClosedByInterruptException e) {
+ return; // do the same stuff as when InterruptedException is thrown
+ } catch (WrappedIOException ex) {
+ handleIOException(key, ex.getConnection(), ex.getIOException());
+ } catch (IOException ex) {
+ handleIOException(key, null, ex);
+ } catch (InterruptedException e) {
+ // FIXME controlled shutdown (e.g. take care of buffermanagement)
+ Thread.currentThread().interrupt();
+ }
+ }
+ } catch (RuntimeException e) {
+ // should hopefully never occur
+ handleFatal(null, e);
+ } finally {
+ doServerShutdown();
+ }
+ }
+
+ /**
+ * Do an additional read
+ *
+ * @throws InterruptedException thrown by taking a buffer
+ * @throws IOException if an error happened during read
+ */
+ private void doAdditionalRead() throws InterruptedException, IOException {
+ WebSocketImpl conn;
+ while (!iqueue.isEmpty()) {
+ conn = iqueue.remove(0);
+ WrappedByteChannel c = ((WrappedByteChannel) conn.getChannel());
+ ByteBuffer buf = takeBuffer();
+ try {
+ if (SocketChannelIOHelper.readMore(buf, conn, c)) {
+ iqueue.add(conn);
+ }
+ if (buf.hasRemaining()) {
+ conn.inQueue.put(buf);
+ queue(conn);
+ } else {
+ pushBuffer(buf);
+ }
+ } catch (IOException e) {
+ pushBuffer(buf);
+ throw e;
+ }
+ }
+ }
+
+ /**
+ * Execute a accept operation
+ *
+ * @param key the selectionkey to read off
+ * @param i the iterator for the selection keys
+ * @throws InterruptedException thrown by taking a buffer
+ * @throws IOException if an error happened during accept
+ */
+ private void doAccept(SelectionKey key, Iterator i)
+ throws IOException, InterruptedException {
+ if (!onConnect(key)) {
+ key.cancel();
+ return;
+ }
+
+ SocketChannel channel = server.accept();
+ if (channel == null) {
+ return;
+ }
+ channel.configureBlocking(false);
+ Socket socket = channel.socket();
+ socket.setTcpNoDelay(isTcpNoDelay());
+ socket.setKeepAlive(true);
+ WebSocketImpl w = wsf.createWebSocket(this, drafts);
+ w.setSelectionKey(channel.register(selector, SelectionKey.OP_READ, w));
+ try {
+ w.setChannel(wsf.wrapChannel(channel, w.getSelectionKey()));
+ i.remove();
+ allocateBuffers(w);
+ } catch (IOException ex) {
+ if (w.getSelectionKey() != null) {
+ w.getSelectionKey().cancel();
+ }
+
+ handleIOException(w.getSelectionKey(), null, ex);
+ }
+ }
+
+ /**
+ * Execute a read operation
+ *
+ * @param key the selectionkey to read off
+ * @param i the iterator for the selection keys
+ * @return true, if the read was successful, or false if there was an error
+ * @throws InterruptedException thrown by taking a buffer
+ * @throws IOException if an error happened during read
+ */
+ private boolean doRead(SelectionKey key, Iterator i)
+ throws InterruptedException, WrappedIOException {
+ WebSocketImpl conn = (WebSocketImpl) key.attachment();
+ ByteBuffer buf = takeBuffer();
+ if (conn.getChannel() == null) {
+ key.cancel();
+
+ handleIOException(key, conn, new IOException());
+ return false;
+ }
+ try {
+ if (SocketChannelIOHelper.read(buf, conn, conn.getChannel())) {
+ if (buf.hasRemaining()) {
+ conn.inQueue.put(buf);
+ queue(conn);
+ i.remove();
+ if (conn.getChannel() instanceof WrappedByteChannel && ((WrappedByteChannel) conn
+ .getChannel()).isNeedRead()) {
+ iqueue.add(conn);
+ }
+ } else {
+ pushBuffer(buf);
+ }
+ } else {
+ pushBuffer(buf);
+ }
+ } catch (IOException e) {
+ pushBuffer(buf);
+ throw new WrappedIOException(conn, e);
+ }
+ return true;
+ }
+
+ /**
+ * Execute a write operation
+ *
+ * @param key the selectionkey to write on
+ * @throws IOException if an error happened during batch
+ */
+ private void doWrite(SelectionKey key) throws WrappedIOException {
+ WebSocketImpl conn = (WebSocketImpl) key.attachment();
+ try {
+ if (SocketChannelIOHelper.batch(conn, conn.getChannel()) && key.isValid()) {
+ key.interestOps(SelectionKey.OP_READ);
+ }
+ } catch (IOException e) {
+ throw new WrappedIOException(conn, e);
+ }
+ }
+
+ /**
+ * Setup the selector thread as well as basic server settings
+ *
+ * @return true, if everything was successful, false if some error happened
+ */
+ private boolean doSetupSelectorAndServerThread() {
+ selectorthread.setName("WebSocketSelector-" + selectorthread.getId());
+ try {
+ if (server == null) {
+ server = ServerSocketChannel.open();
+ // If 'server' is not null, that means WebSocketServer was created from existing channel.
+ }
+ server.configureBlocking(false);
+ ServerSocket socket = server.socket();
+ int receiveBufferSize = getReceiveBufferSize();
+ if (receiveBufferSize > 0) {
+ socket.setReceiveBufferSize(receiveBufferSize);
+ }
+ socket.setReuseAddress(isReuseAddr());
+ // Socket may be already bound, if an existing channel was passed to constructor.
+ // In this case we cannot modify backlog size from pure Java code, so leave it as is.
+ if (!socket.isBound()) {
+ socket.bind(address, getMaxPendingConnections());
+ }
+ selector = Selector.open();
+ server.register(selector, server.validOps());
+ startConnectionLostTimer();
+ for (WebSocketWorker ex : decoders) {
+ ex.start();
+ }
+ onStart();
+ } catch (IOException ex) {
+ handleFatal(null, ex);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * The websocket server can only be started once
+ *
+ * @return true, if the server can be started, false if already a thread is running
+ */
+ private boolean doEnsureSingleThread() {
+ synchronized (this) {
+ if (selectorthread != null) {
+ throw new IllegalStateException(getClass().getName() + " can only be started once.");
+ }
+ selectorthread = Thread.currentThread();
+ if (isclosed.get()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Clean up everything after a shutdown
+ */
+ private void doServerShutdown() {
+ stopConnectionLostTimer();
+ if (decoders != null) {
+ for (WebSocketWorker w : decoders) {
+ w.interrupt();
+ }
+ }
+ if (selector != null) {
+ try {
+ selector.close();
+ } catch (IOException e) {
+ log.error("IOException during selector.close", e);
+ onError(null, e);
+ }
+ }
+ if (server != null) {
+ try {
+ server.close();
+ } catch (IOException e) {
+ log.error("IOException during server.close", e);
+ onError(null, e);
+ }
+ }
+ }
+
+ protected void allocateBuffers(WebSocket c) throws InterruptedException {
+ if (queuesize.get() >= 2 * decoders.size() + 1) {
+ return;
+ }
+ queuesize.incrementAndGet();
+ buffers.put(createBuffer());
+ }
+
+ protected void releaseBuffers(WebSocket c) throws InterruptedException {
+ // queuesize.decrementAndGet();
+ // takeBuffer();
+ }
+
+ public ByteBuffer createBuffer() {
+ int receiveBufferSize = getReceiveBufferSize();
+ return ByteBuffer.allocate(receiveBufferSize > 0 ? receiveBufferSize : DEFAULT_READ_BUFFER_SIZE);
+ }
+
+ protected void queue(WebSocketImpl ws) throws InterruptedException {
+ if (ws.getWorkerThread() == null) {
+ ws.setWorkerThread(decoders.get(queueinvokes % decoders.size()));
+ queueinvokes++;
+ }
+ ws.getWorkerThread().put(ws);
+ }
+
+ private ByteBuffer takeBuffer() throws InterruptedException {
+ return buffers.take();
+ }
+
+ private void pushBuffer(ByteBuffer buf) throws InterruptedException {
+ if (buffers.size() > queuesize.intValue()) {
+ return;
+ }
+ buffers.put(buf);
+ }
+
+ private void handleIOException(SelectionKey key, WebSocket conn, IOException ex) {
+ // onWebsocketError( conn, ex );// conn may be null here
+ if (key != null) {
+ key.cancel();
+ }
+ if (conn != null) {
+ conn.closeConnection(CloseFrame.ABNORMAL_CLOSE, ex.getMessage());
+ } else if (key != null) {
+ SelectableChannel channel = key.channel();
+ if (channel != null && channel
+ .isOpen()) { // this could be the case if the IOException ex is a SSLException
+ try {
+ channel.close();
+ } catch (IOException e) {
+ // there is nothing that must be done here
+ }
+ log.trace("Connection closed because of exception", ex);
+ }
+ }
+ }
+
+ private void handleFatal(WebSocket conn, Exception e) {
+ log.error("Shutdown due to fatal error", e);
+ onError(conn, e);
+
+ String causeMessage = e.getCause() != null ? " caused by " + e.getCause().getClass().getName() : "";
+ String errorMessage = "Got error on server side: " + e.getClass().getName() + causeMessage;
+ try {
+ stop(0, errorMessage);
+ } catch (InterruptedException e1) {
+ Thread.currentThread().interrupt();
+ log.error("Interrupt during stop", e);
+ onError(null, e1);
+ }
+
+ //Shutting down WebSocketWorkers, see #222
+ if (decoders != null) {
+ for (WebSocketWorker w : decoders) {
+ w.interrupt();
+ }
+ }
+ if (selectorthread != null) {
+ selectorthread.interrupt();
+ }
+ }
+
+ @Override
+ public final void onWebsocketMessage(WebSocket conn, String message) {
+ onMessage(conn, message);
+ }
+
+
+ @Override
+ public final void onWebsocketMessage(WebSocket conn, ByteBuffer blob) {
+ onMessage(conn, blob);
+ }
+
+ @Override
+ public final void onWebsocketOpen(WebSocket conn, Handshakedata handshake) {
+ if (addConnection(conn)) {
+ onOpen(conn, (ClientHandshake) handshake);
+ }
+ }
+
+ @Override
+ public final void onWebsocketClose(WebSocket conn, int code, String reason, boolean remote) {
+ selector.wakeup();
+ try {
+ if (removeConnection(conn)) {
+ onClose(conn, code, reason, remote);
+ }
+ } finally {
+ try {
+ releaseBuffers(conn);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ }
+
+ /**
+ * This method performs remove operations on the connection and therefore also gives control over
+ * whether the operation shall be synchronized
+ *
+ * {@link #WebSocketServer(InetSocketAddress, int, List, Collection)} allows to specify a
+ * collection which will be used to store current connections in.