diff --git a/.gitignore b/.gitignore
index 3f7974706..d7e41aae5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,13 @@
*.class
+.git
+.gradle
+build
*.svn
*~
target
+*.ipr
+*.iml
+*.iws
.settings
.project
.classpath
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 000000000..067c53e42
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,5 @@
+language: java
+jdk:
+ - oraclejdk7
+
+script: "./gradlew test"
diff --git a/README.markdown b/README.markdown
index 414ec32b6..0aaab7a2f 100644
--- a/README.markdown
+++ b/README.markdown
@@ -1,3 +1,4 @@
+[](https://travis-ci.org/ck1125/Java-WebSocket)
Java WebSockets
===============
@@ -32,8 +33,14 @@ The ant targets are: ```compile```, ```jar```, ```doc``` and ```clean```
###Maven
-Maven is supported. More documentation in that is yet to come...
-
+To use maven just add this dependency to your pom.xml:
+```xml
+
+ org.java-websocket
+ Java-WebSocket
+ 1.3.0
+
+```
Running the Examples
-------------------
@@ -90,7 +97,7 @@ To see how to use wss please take a look at the examples.
If you do not have a valid **certificate** in place then you will have to create a self signed one.
Browsers will simply refuse the connection in case of a bad certificate and will not ask the user to accept it.
So the first step will be to make a browser to accept your self signed certificate. ( https://bugzilla.mozilla.org/show_bug.cgi?id=594502 ).
-If the websocket server url is `wss://localhost:8000` visit the url `htts://localhost:8000` with your browser. The browser will recognize the handshake and allow you to accept the certificate.
+If the websocket server url is `wss://localhost:8000` visit the url `https://localhost:8000` with your browser. The browser will recognize the handshake and allow you to accept the certificate. This technique is also demonstrated in this [video](http://www.youtube.com/watch?v=F8lBdfAZPkU).
The vm option `-Djavax.net.debug=all` can help to find out if there is a problem with the certificate.
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 000000000..a87d144f7
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,44 @@
+apply plugin: 'java'
+apply plugin: 'idea'
+apply plugin: 'maven'
+
+
+repositories {
+ mavenLocal()
+ mavenCentral()
+}
+
+group = 'org.java_websocket'
+version = '1.2.1-SNAPSHOT'
+sourceCompatibility = 1.6
+targetCompatibility = 1.6
+
+configurations {
+ deployerJars
+}
+
+configure(install.repositories.mavenInstaller) {
+ pom.version = project.version
+ pom.groupId = "org.java_websocket"
+ pom.artifactId = 'Java-WebSocket'
+}
+
+dependencies {
+ deployerJars "org.apache.maven.wagon:wagon-webdav:1.0-beta-2"
+}
+
+
+//deploy to maven repository
+//uploadArchives {
+// repositories.mavenDeployer {
+// configuration = configurations.deployerJars
+// repository(url: repositoryUrl) {
+// authentication(userName: repositoryUsername, password: repositoryPassword)
+// }
+// }
+//}
+
+task wrapper(type: Wrapper) {
+ gradleVersion = '1.4'
+}
+
diff --git a/dist/java_websocket.jar b/dist/java_websocket.jar
index bb5caeb4e..7134f4c11 100644
Binary files a/dist/java_websocket.jar and b/dist/java_websocket.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..cf8ca39e9
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..3d698f898
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Feb 13 00:13:35 GMT 2013
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=http\://services.gradle.org/distributions/gradle-1.4-bin.zip
diff --git a/gradlew b/gradlew
new file mode 100755
index 000000000..ae91ed902
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,164 @@
+#!/bin/bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/"
+APP_HOME="`pwd -P`"
+cd "$SAVED"
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query businessSystem maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ JAVA_OPTS="$JAVA_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 000000000..8a0b282aa
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/pom.xml b/pom.xml
index 2d4f42b32..9189fba8d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,13 +1,23 @@
-
+
+
+ org.sonatype.oss
+ oss-parent
+ 7
+
+
+ scm:git:git@github.com:TooTallNate/Java-WebSocket.git
+ scm:git:git@github.com:TooTallNate/Java-WebSocket.git
+ git@github.com:TooTallNate/Java-WebSocket.git
+
4.0.0
- org.java_websocket
+ org.java-websocket
Java-WebSocket
- 1.0.0-SNAPSHOT
+ 1.3.1-SNAPSHOT
jar
Java WebSocket
http://java-websocket.org/
+ A barebones WebSocket client and server implementation written in 100% Java
UTF-8
1.6
@@ -23,6 +33,87 @@
${java.version}
+
+ org.apache.maven.plugins
+ maven-source-plugin
+ 2.2.1
+
+
+ attach-sources
+
+ jar
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ 2.9
+
+
+ attach-javadocs
+
+ jar
+
+
+
+
-
\ No newline at end of file
+
+
+ TooTallNate
+ Nathan Rajlich
+ nathan@tootallnate.net
+ https://github.com/TooTallNate
+
+ founder
+
+
+
+ Davidiusdadi
+ David Rohmer
+ rohmer.david@gmail.com
+ https://github.com/Davidiusdadi
+
+ maintainer
+
+
+
+
+
+ MIT License
+ http://github.com/TooTallNate/Java-WebSocket/blob/master/LICENSE
+
+
+
+
+ release-sign-artifacts
+
+ performReleasetrue
+
+
+
+
+ org.apache.maven.plugins
+ maven-gpg-plugin
+ 1.1
+
+
+ sign-artifacts
+ verify
+
+ sign
+
+
+
+
+ rohmer.david@gmail.com
+
+
+
+
+
+
+
diff --git a/src/main/example/ChatClient.java b/src/main/example/ChatClient.java
index ee2bdbe24..96a19100d 100644
--- a/src/main/example/ChatClient.java
+++ b/src/main/example/ChatClient.java
@@ -13,7 +13,7 @@
import javax.swing.JTextArea;
import javax.swing.JTextField;
-import org.java_websocket.WebSocket;
+import org.java_websocket.WebSocketImpl;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.drafts.Draft;
import org.java_websocket.drafts.Draft_10;
@@ -148,7 +148,7 @@ public void onError( Exception ex ) {
}
public static void main( String[] args ) {
- WebSocket.DEBUG = true;
+ WebSocketImpl.DEBUG = true;
String location;
if( args.length != 0 ) {
location = args[ 0 ];
diff --git a/src/main/example/ChatServer.java b/src/main/example/ChatServer.java
index efe6dd2d8..a27cb0ee0 100644
--- a/src/main/example/ChatServer.java
+++ b/src/main/example/ChatServer.java
@@ -5,8 +5,9 @@
import java.net.UnknownHostException;
import java.util.Collection;
-import org.java_websocket.IWebSocket;
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;
@@ -24,25 +25,30 @@ public ChatServer( InetSocketAddress address ) {
}
@Override
- public void onOpen( IWebSocket conn, ClientHandshake handshake ) {
+ public void onOpen( WebSocket conn, ClientHandshake handshake ) {
this.sendToAll( "new connection: " + handshake.getResourceDescriptor() );
System.out.println( conn.getRemoteSocketAddress().getAddress().getHostAddress() + " entered the room!" );
}
@Override
- public void onClose( IWebSocket conn, int code, String reason, boolean remote ) {
+ public void onClose( WebSocket conn, int code, String reason, boolean remote ) {
this.sendToAll( conn + " has left the room!" );
System.out.println( conn + " has left the room!" );
}
@Override
- public void onMessage( IWebSocket conn, String message ) {
+ public void onMessage( WebSocket conn, String message ) {
this.sendToAll( message );
System.out.println( conn + ": " + message );
}
+ @Override
+ public void onFragment( WebSocket conn, Framedata fragment ) {
+ System.out.println( "received fragment: " + fragment );
+ }
+
public static void main( String[] args ) throws InterruptedException , IOException {
- WebSocket.DEBUG = true;
+ WebSocketImpl.DEBUG = true;
int port = 8887; // 843 flash policy port
try {
port = Integer.parseInt( args[ 0 ] );
@@ -56,11 +62,18 @@ public static void main( String[] args ) throws InterruptedException , IOExcepti
while ( true ) {
String in = sysin.readLine();
s.sendToAll( in );
+ if( in.equals( "exit" ) ) {
+ s.stop();
+ break;
+ } else if( in.equals( "restart" ) ) {
+ s.stop();
+ s.start();
+ break;
+ }
}
}
-
@Override
- public void onError( IWebSocket conn, Exception ex ) {
+ 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
@@ -76,9 +89,9 @@ public void onError( IWebSocket conn, Exception ex ) {
* When socket related I/O errors occur.
*/
public void sendToAll( String text ) {
- Collection con = connections();
+ Collection con = connections();
synchronized ( con ) {
- for( IWebSocket c : con ) {
+ for( WebSocket c : con ) {
c.send( text );
}
}
diff --git a/src/main/example/ExampleClient.java b/src/main/example/ExampleClient.java
index 531784528..3cf68377e 100644
--- a/src/main/example/ExampleClient.java
+++ b/src/main/example/ExampleClient.java
@@ -4,6 +4,7 @@
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.drafts.Draft;
import org.java_websocket.drafts.Draft_10;
+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. */
@@ -20,13 +21,17 @@ public ExampleClient( URI serverURI ) {
@Override
public void onOpen( ServerHandshake handshakedata ) {
System.out.println( "opened connection" );
- // if you pan to refuse connection based on ip or httpfields overload: onWebsocketHandshakeReceivedAsClient
+ // if you plan to refuse connection based on ip or httpfields overload: onWebsocketHandshakeReceivedAsClient
}
@Override
public void onMessage( String message ) {
System.out.println( "received: " + message );
- // send( "you said: " + message );
+ }
+
+ @Override
+ public void onFragment( Framedata fragment ) {
+ System.out.println( "received fragment: " + new String( fragment.getPayloadData().array() ) );
}
@Override
diff --git a/src/main/example/FragmentedFramesExample.java b/src/main/example/FragmentedFramesExample.java
new file mode 100644
index 000000000..b5a03e1aa
--- /dev/null
+++ b/src/main/example/FragmentedFramesExample.java
@@ -0,0 +1,57 @@
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+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_17;
+import org.java_websocket.framing.Framedata.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.
+ *
+ * @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" ), new Draft_17() ); // Draft_17 is implementation of rfc6455
+ 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." );
+
+ 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;
+ }
+
+ }
+ 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/ProxyClientExample.java b/src/main/example/ProxyClientExample.java
new file mode 100644
index 000000000..ddff0ea0d
--- /dev/null
+++ b/src/main/example/ProxyClientExample.java
@@ -0,0 +1,12 @@
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+import java.net.URI;
+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();
+ }
+}
diff --git a/src/main/example/SSLClientExample.java b/src/main/example/SSLClientExample.java
index 80ec555fa..e740c9c54 100644
--- a/src/main/example/SSLClientExample.java
+++ b/src/main/example/SSLClientExample.java
@@ -7,10 +7,10 @@
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;
-import org.java_websocket.WebSocket;
-import org.java_websocket.client.DefaultSSLWebSocketClientFactory;
+import org.java_websocket.WebSocketImpl;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
@@ -55,7 +55,7 @@ public class SSLClientExample {
*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 {
- WebSocket.DEBUG = true;
+ WebSocketImpl.DEBUG = true;
WebSocketChatClient chatclient = new WebSocketChatClient( new URI( "wss://localhost:8887" ) );
@@ -79,7 +79,9 @@ public static void main( String[] args ) throws Exception {
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
- chatclient.setWebSocketFactory( new DefaultSSLWebSocketClientFactory( sslContext ) );
+ SSLSocketFactory factory = sslContext.getSocketFactory();// (SSLSocketFactory) SSLSocketFactory.getDefault();
+
+ chatclient.setSocket( factory.createSocket() );
chatclient.connectBlocking();
diff --git a/src/main/example/SSLServerExample.java b/src/main/example/SSLServerExample.java
index 9ffb6ce21..563395403 100644
--- a/src/main/example/SSLServerExample.java
+++ b/src/main/example/SSLServerExample.java
@@ -8,7 +8,7 @@
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
-import org.java_websocket.WebSocket;
+import org.java_websocket.WebSocketImpl;
import org.java_websocket.server.DefaultSSLWebSocketServerFactory;
public class SSLServerExample {
@@ -19,7 +19,7 @@ public class SSLServerExample {
*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 {
- WebSocket.DEBUG = true;
+ WebSocketImpl.DEBUG = true;
ChatServer chatserver = new ChatServer( 8887 ); // Firefox does allow multible ssl connection only via port 443 //tested on FF16
diff --git a/src/main/java/org/java_websocket/AbstractWrappedByteChannel.java b/src/main/java/org/java_websocket/AbstractWrappedByteChannel.java
new file mode 100644
index 000000000..0481a6d4a
--- /dev/null
+++ b/src/main/java/org/java_websocket/AbstractWrappedByteChannel.java
@@ -0,0 +1,75 @@
+package org.java_websocket;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.ByteChannel;
+import java.nio.channels.SocketChannel;
+
+import javax.net.ssl.SSLException;
+
+
+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() : false;
+ }
+
+ @Override
+ public void writeMore() throws IOException {
+ if( channel instanceof WrappedByteChannel )
+ ( (WrappedByteChannel) channel ).writeMore();
+
+ }
+
+ @Override
+ public boolean isNeedRead() {
+ return channel instanceof WrappedByteChannel ? ( (WrappedByteChannel) channel ).isNeedRead() : false;
+
+ }
+
+ @Override
+ public int readMore( ByteBuffer dst ) throws SSLException {
+ 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/IWebSocket.java b/src/main/java/org/java_websocket/IWebSocket.java
deleted file mode 100644
index ffceacc14..000000000
--- a/src/main/java/org/java_websocket/IWebSocket.java
+++ /dev/null
@@ -1,101 +0,0 @@
-package org.java_websocket;
-
-import java.net.InetSocketAddress;
-import java.nio.ByteBuffer;
-import java.nio.channels.NotYetConnectedException;
-
-import org.java_websocket.drafts.Draft;
-import org.java_websocket.exceptions.InvalidDataException;
-import org.java_websocket.exceptions.InvalidHandshakeException;
-import org.java_websocket.framing.Framedata;
-import org.java_websocket.handshake.ClientHandshakeBuilder;
-
-public interface IWebSocket {
-
- enum Role {
- CLIENT,
- SERVER
- }
-
- enum READYSTATE {
- NOT_YET_CONNECTED,
- CONNECTING,
- OPEN,
- CLOSING,
- CLOSED;
- }
-
- static int RCVBUF = 65536;
-
- /**
- * 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;
- int DEFAULT_WSS_PORT = 443;
-
- /**
- * sends the closing handshake.
- * may be send in response to an other handshake.
- */
- abstract void close(int code, String message);
-
- void close(int code);
-
- /**
- * 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.
- **/
- void closeConnection(int code, String message);
-
- void close(InvalidDataException e);
-
- /**
- * Send Text data to the other end.
- *
- * @throws IllegalArgumentException
- * @throws NotYetConnectedException
- */
- void send(String text) throws NotYetConnectedException;
-
- /**
- * Send Binary data (plain bytes) to the other end.
- *
- * @throws IllegalArgumentException
- * @throws NotYetConnectedException
- */
- void send(ByteBuffer bytes) throws IllegalArgumentException, NotYetConnectedException;
-
- void send(byte[] bytes) throws IllegalArgumentException, NotYetConnectedException;
-
- void sendFrame(Framedata framedata);
-
- boolean hasBufferedData();
-
- void startHandshake(ClientHandshakeBuilder handshakedata) throws InvalidHandshakeException;
-
- InetSocketAddress getRemoteSocketAddress();
-
- InetSocketAddress getLocalSocketAddress();
-
- boolean isConnecting();
-
- boolean isOpen();
-
- boolean isClosing();
-
- boolean isClosed();
-
- 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();
-
-}
\ 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 4cb3d2712..608cec4d6 100644
--- a/src/main/java/org/java_websocket/SSLSocketChannel2.java
+++ b/src/main/java/org/java_websocket/SSLSocketChannel2.java
@@ -16,6 +16,7 @@
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
@@ -30,6 +31,9 @@
* Implements the relevant portions of the SocketChannel interface with the SSLEngine wrapper.
*/
public class SSLSocketChannel2 implements ByteChannel, WrappedByteChannel {
+ /**
+ * 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;
@@ -43,31 +47,67 @@ public class SSLSocketChannel2 implements ByteChannel, WrappedByteChannel {
/** encrypted data incoming */
protected ByteBuffer inCrypt;
- protected SocketChannel sc;
- protected SelectionKey key;
+ /** the underlying channel */
+ protected SocketChannel socketChannel;
+ /** used to set interestOP SelectionKey.OP_WRITE for the underlying channel */
+ protected SelectionKey selectionKey;
- protected SSLEngineResult res;
protected SSLEngine sslEngine;
+ protected SSLEngineResult readEngineResult;
+ protected SSLEngineResult writeEngineResult;
- public SSLSocketChannel2( SelectionKey key , SSLEngine sslEngine , ExecutorService exec ) throws IOException {
- this.sc = (SocketChannel) key.channel();
- this.key = key;
- this.sslEngine = sslEngine;
- this.exec = exec;
+ /**
+ * 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;
- tasks = new ArrayList>( 3 );
+ 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.key.interestOps( key.interestOps() | SelectionKey.OP_WRITE );
+ this.socketChannel = channel;
+ this.sslEngine = sslEngine;
+ this.exec = exec;
- sslEngine.setEnableSessionCreation( true );
- SSLSession session = sslEngine.getSession();
- createBuffers( session );
+ readEngineResult = writeEngineResult = new SSLEngineResult( Status.BUFFER_UNDERFLOW, sslEngine.getHandshakeStatus(), 0, 0 ); // init to prevent NPEs
- sc.write( wrap( emptybuffer ) );// initializes res
+ 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();
}
- private void processHandshake() throws IOException {
+ 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 );
+ }
+ }
+
+ /**
+ * 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() ) {
@@ -75,42 +115,57 @@ private void processHandshake() throws IOException {
if( f.isDone() ) {
it.remove();
} else {
+ if( isBlocking() )
+ consumeFutureUninterruptible( f );
return;
}
}
}
- if( res.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP ) {
- inCrypt.compact();
- int read = sc.read( inCrypt );
- if( read == -1 ) {
- throw new IOException( "connection closed unexpectedly by peer" );
+ 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();
}
- inCrypt.flip();
inData.compact();
unwrap();
+ if( readEngineResult.getHandshakeStatus() == HandshakeStatus.FINISHED ) {
+ createBuffers( sslEngine.getSession() );
+ return;
+ }
}
consumeDelegatedTasks();
- if( tasks.isEmpty() || res.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_WRAP ) {
- sc.write( wrap( emptybuffer ) );
+ 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
+ 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();
- res = sslEngine.wrap( b, outCrypt );
+ writeEngineResult = sslEngine.wrap( b, outCrypt );
outCrypt.flip();
return outCrypt;
}
+ /**
+ * performs the unwrap operation by unwrapping from {@link #inCrypt} to {@link #inData}
+ **/
private synchronized ByteBuffer unwrap() throws SSLException {
int rem;
- do{
+ do {
rem = inData.remaining();
- res = sslEngine.unwrap( inCrypt, inData );
- } while ( 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;
}
@@ -118,7 +173,12 @@ private synchronized ByteBuffer unwrap() throws SSLException {
protected void consumeDelegatedTasks() {
Runnable task;
while ( ( task = sslEngine.getDelegatedTask() ) != null ) {
- tasks.add( exec.submit( task ) );
+ // JBW - 19FEB14: Sometimes we get tasks after the exec is shutdown. Causes ScheduledThreadPoolExecutor (and NPE other places).
+ if ((!exec.isShutdown()) && (!exec.isTerminated())) {
+ tasks.add( exec.submit( task ) );
+ } else {
+ System.out.println("Exec shutdown in consumerDelegatedTasks");
+ }
// task.run();
}
}
@@ -127,12 +187,25 @@ protected void createBuffers( SSLSession session ) {
int appBufferMax = session.getApplicationBufferSize();
int netBufferMax = session.getPacketBufferSize();
- inData = ByteBuffer.allocate( appBufferMax );
- outCrypt = ByteBuffer.allocate( netBufferMax );
- inCrypt = ByteBuffer.allocate( 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 );
+ }
+ inData.rewind();
inData.flip();
+ inCrypt.rewind();
inCrypt.flip();
+ outCrypt.rewind();
outCrypt.flip();
+ bufferallocations++;
}
public int write( ByteBuffer src ) throws IOException {
@@ -140,21 +213,50 @@ public int write( ByteBuffer src ) throws IOException {
processHandshake();
return 0;
}
- int num = sc.write( wrap( src ) );
+ // assert ( bufferallocations > 1 ); //see #190
+ if( bufferallocations <= 1 ) {
+ createBuffers( sslEngine.getSession() );
+ }
+ int num = socketChannel.write( wrap( src ) );
return num;
}
+ /**
+ * 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 {
- if( !isHandShakeComplete() ) {
- processHandshake();
+ 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();
@@ -163,23 +265,28 @@ public int read( ByteBuffer dst ) throws IOException {
else
inCrypt.compact();
- if( sc.read( inCrypt ) == -1 ) {
- return -1;
- }
+ if( isBlocking() || readEngineResult.getStatus() == Status.BUFFER_UNDERFLOW )
+ if( socketChannel.read( inCrypt ) == -1 ) {
+ return -1;
+ }
inCrypt.flip();
unwrap();
- return transfereTo( inData, dst );
+ int transfered = transfereTo( inData, dst );
+ if( transfered == 0 && isBlocking() ) {
+ return read( dst ); // "transfered" may be 0 when not enough bytes were received or during rehandshaking
+ }
+ return transfered;
}
-
+ /**
+ * {@link #read(ByteBuffer)} may not be to leave all buffers(inData, inCrypt)
+ **/
private int readRemaining( ByteBuffer dst ) throws SSLException {
- assert ( dst.hasRemaining() );
-
if( inData.hasRemaining() ) {
return transfereTo( inData, dst );
}
- assert ( !inData.hasRemaining() );
- inData.clear();
+ if( !inData.hasRemaining() )
+ inData.clear();
// test if some bytes left from last read (e.g. BUFFER_UNDERFLOW)
if( inCrypt.hasRemaining() ) {
unwrap();
@@ -191,36 +298,37 @@ private int readRemaining( ByteBuffer dst ) throws SSLException {
}
public boolean isConnected() {
- return sc.isConnected();
+ return socketChannel.isConnected();
}
public void close() throws IOException {
sslEngine.closeOutbound();
sslEngine.getSession().invalidate();
- if( sc.isOpen() )
- sc.write( wrap( emptybuffer ) );// FIXME what if not all bytes can be written
- sc.close();
+ if( socketChannel.isOpen() )
+ socketChannel.write( wrap( emptybuffer ) );// FIXME what if not all bytes can be written
+ socketChannel.close();
+ exec.shutdownNow();
}
private boolean isHandShakeComplete() {
- HandshakeStatus status = res.getHandshakeStatus();
+ HandshakeStatus status = sslEngine.getHandshakeStatus();
return status == SSLEngineResult.HandshakeStatus.FINISHED || status == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING;
}
public SelectableChannel configureBlocking( boolean b ) throws IOException {
- return sc.configureBlocking( b );
+ return socketChannel.configureBlocking( b );
}
public boolean connect( SocketAddress remote ) throws IOException {
- return sc.connect( remote );
+ return socketChannel.connect( remote );
}
public boolean finishConnect() throws IOException {
- return sc.finishConnect();
+ return socketChannel.finishConnect();
}
public Socket socket() {
- return sc.socket();
+ return socketChannel.socket();
}
public boolean isInboundDone() {
@@ -229,12 +337,12 @@ public boolean isInboundDone() {
@Override
public boolean isOpen() {
- return sc.isOpen();
+ return socketChannel.isOpen();
}
@Override
public boolean isNeedWrite() {
- return outCrypt.hasRemaining() || !isHandShakeComplete();
+ return outCrypt.hasRemaining() || !isHandShakeComplete(); // FIXME this condition can cause high cpu load during handshaking when network is slow
}
@Override
@@ -244,7 +352,7 @@ public void writeMore() throws IOException {
@Override
public boolean isNeedRead() {
- return inData.hasRemaining() || ( inCrypt.hasRemaining() && res.getStatus() != Status.BUFFER_UNDERFLOW );
+ return inData.hasRemaining() || ( inCrypt.hasRemaining() && readEngineResult.getStatus() != Status.BUFFER_UNDERFLOW && readEngineResult.getStatus() != Status.CLOSED );
}
@Override
@@ -269,4 +377,9 @@ private int transfereTo( ByteBuffer from, ByteBuffer to ) {
}
+ @Override
+ public boolean isBlocking() {
+ return socketChannel.isBlocking();
+ }
+
}
\ 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 a8dfc8696..5dcb93c5e 100644
--- a/src/main/java/org/java_websocket/SocketChannelIOHelper.java
+++ b/src/main/java/org/java_websocket/SocketChannelIOHelper.java
@@ -3,6 +3,9 @@
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ByteChannel;
+import java.nio.channels.spi.AbstractSelectableChannel;
+
+import org.java_websocket.WebSocket.Role;
public class SocketChannelIOHelper {
@@ -18,6 +21,10 @@ public static boolean read( final ByteBuffer buf, WebSocketImpl ws, ByteChannel
return read != 0;
}
+ /**
+ * @see WrappedByteChannel#readMore(ByteBuffer)
+ * @return returns whether there is more data left which can be obtained via {@link #readMore(ByteBuffer, WebSocketImpl, WrappedByteChannel)}
+ **/
public static boolean readMore( final ByteBuffer buf, WebSocketImpl ws, WrappedByteChannel channel ) throws IOException {
buf.clear();
int read = channel.readMore( buf );
@@ -35,10 +42,11 @@ public static boolean readMore( final ByteBuffer buf, WebSocketImpl ws, WrappedB
/** Returns whether the whole outQueue has been flushed */
public static boolean batch( WebSocketImpl ws, ByteChannel sockchannel ) throws IOException {
ByteBuffer buffer = ws.outQueue.peek();
+ WrappedByteChannel c = null;
if( buffer == null ) {
if( sockchannel instanceof WrappedByteChannel ) {
- WrappedByteChannel c = (WrappedByteChannel) sockchannel;
+ c = (WrappedByteChannel) sockchannel;
if( c.isNeedWrite() ) {
c.writeMore();
}
@@ -55,12 +63,11 @@ public static boolean batch( WebSocketImpl ws, ByteChannel sockchannel ) throws
} while ( buffer != null );
}
- if( ws.outQueue.isEmpty() && ws.isFlushAndClose() ) {
+ if( ws.outQueue.isEmpty() && ws.isFlushAndClose() && ws.getDraft().getRole() == WebSocket.Role.SERVER ) {//
synchronized ( ws ) {
ws.closeConnection();
}
}
- return sockchannel instanceof WrappedByteChannel == true ? !( (WrappedByteChannel) sockchannel ).isNeedWrite() : true;
+ return c != null ? !( (WrappedByteChannel) sockchannel ).isNeedWrite() : true;
}
-
}
diff --git a/src/main/java/org/java_websocket/WebSocket.java b/src/main/java/org/java_websocket/WebSocket.java
index ebd67a1f2..a661eddbf 100644
--- a/src/main/java/org/java_websocket/WebSocket.java
+++ b/src/main/java/org/java_websocket/WebSocket.java
@@ -1,17 +1,21 @@
package org.java_websocket;
import java.net.InetSocketAddress;
-import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.NotYetConnectedException;
import org.java_websocket.drafts.Draft;
-import org.java_websocket.exceptions.InvalidDataException;
import org.java_websocket.framing.Framedata;
+import org.java_websocket.framing.Framedata.Opcode;
-public abstract class WebSocket implements IWebSocket {
+public interface WebSocket {
+ public enum Role {
+ CLIENT, SERVER
+ }
- public static/*final*/boolean DEBUG = false; // must be final in the future in order to take advantage of VM optimization
+ public enum READYSTATE {
+ NOT_YET_CONNECTED, CONNECTING, OPEN, CLOSING, CLOSED;
+ }
/**
* The default port of WebSockets, as defined in the spec. If the nullary
@@ -26,9 +30,12 @@ public abstract class WebSocket implements IWebSocket {
* sends the closing handshake.
* may be send in response to an other handshake.
*/
- public abstract void close( int code, String message );
+ public void close( int code, String message );
- public abstract void close( int code );
+ public void close( int code );
+
+ /** Convenience function which behaves like close(CloseFrame.NORMAL) */
+ public void close();
/**
* This will close the connection immediately without a proper close handshake.
@@ -36,8 +43,6 @@ public abstract class WebSocket implements IWebSocket {
**/
public abstract void closeConnection( int code, String message );
- public abstract void close( InvalidDataException e );
-
/**
* Send Text data to the other end.
*
@@ -58,17 +63,30 @@ public abstract class WebSocket implements IWebSocket {
public abstract void sendFrame( Framedata framedata );
+ /**
+ * 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.
+ **/
+ public abstract void sendFragmentedFrame( Opcode op, ByteBuffer buffer, boolean fin );
+
public abstract boolean hasBufferedData();
/**
- * @returns null when connections is closed
- * @see Socket#getRemoteSocketAddress()
+ * @returns never returns null
*/
public abstract InetSocketAddress getRemoteSocketAddress();
/**
- * @returns null when connections is closed
- * @see Socket#getLocalSocketAddress()
+ * @returns never returns null
*/
public abstract InetSocketAddress getLocalSocketAddress();
@@ -97,4 +115,10 @@ public abstract class WebSocket implements IWebSocket {
* @return Returns '0 = CONNECTING', '1 = OPEN', '2 = CLOSING' or '3 = CLOSED'
*/
public abstract 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.
+ **/
+ public abstract String getResourceDescriptor();
}
\ 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 948598c7a..290e1049a 100644
--- a/src/main/java/org/java_websocket/WebSocketAdapter.java
+++ b/src/main/java/org/java_websocket/WebSocketAdapter.java
@@ -1,7 +1,10 @@
package org.java_websocket;
+import java.net.InetSocketAddress;
+
import org.java_websocket.drafts.Draft;
import org.java_websocket.exceptions.InvalidDataException;
+import org.java_websocket.exceptions.InvalidHandshakeException;
import org.java_websocket.framing.Framedata;
import org.java_websocket.framing.Framedata.Opcode;
import org.java_websocket.framing.FramedataImpl1;
@@ -21,12 +24,12 @@ public abstract class WebSocketAdapter implements WebSocketListener {
* @see org.java_websocket.WebSocketListener#onWebsocketHandshakeReceivedAsServer(WebSocket, Draft, ClientHandshake)
*/
@Override
- public ServerHandshakeBuilder onWebsocketHandshakeReceivedAsServer( IWebSocket conn, Draft draft, ClientHandshake request ) throws InvalidDataException {
+ public ServerHandshakeBuilder onWebsocketHandshakeReceivedAsServer( WebSocket conn, Draft draft, ClientHandshake request ) throws InvalidDataException {
return new HandshakeImpl1Server();
}
@Override
- public void onWebsocketHandshakeReceivedAsClient( IWebSocket conn, ClientHandshake request, ServerHandshake response ) throws InvalidDataException {
+ public void onWebsocketHandshakeReceivedAsClient( WebSocket conn, ClientHandshake request, ServerHandshake response ) throws InvalidDataException {
}
/**
@@ -35,7 +38,7 @@ public void onWebsocketHandshakeReceivedAsClient( IWebSocket conn, ClientHandsha
* @see org.java_websocket.WebSocketListener#onWebsocketHandshakeSentAsClient(WebSocket, ClientHandshake)
*/
@Override
- public void onWebsocketHandshakeSentAsClient( IWebSocket conn, ClientHandshake request ) throws InvalidDataException {
+ public void onWebsocketHandshakeSentAsClient( WebSocket conn, ClientHandshake request ) throws InvalidDataException {
}
/**
@@ -44,7 +47,7 @@ public void onWebsocketHandshakeSentAsClient( IWebSocket conn, ClientHandshake r
* @see org.java_websocket.WebSocketListener#onWebsocketMessageFragment(WebSocket, Framedata)
*/
@Override
- public void onWebsocketMessageFragment( IWebSocket conn, Framedata frame ) {
+ public void onWebsocketMessageFragment( WebSocket conn, Framedata frame ) {
}
/**
@@ -54,7 +57,7 @@ public void onWebsocketMessageFragment( IWebSocket conn, Framedata frame ) {
* @see org.java_websocket.WebSocketListener#onWebsocketPing(WebSocket, Framedata)
*/
@Override
- public void onWebsocketPing( IWebSocket conn, Framedata f ) {
+ public void onWebsocketPing( WebSocket conn, Framedata f ) {
FramedataImpl1 resp = new FramedataImpl1( f );
resp.setOptcode( Opcode.PONG );
conn.sendFrame( resp );
@@ -66,7 +69,7 @@ public void onWebsocketPing( IWebSocket conn, Framedata f ) {
* @see @see org.java_websocket.WebSocketListener#onWebsocketPong(WebSocket, Framedata)
*/
@Override
- public void onWebsocketPong( IWebSocket conn, Framedata f ) {
+ public void onWebsocketPong( WebSocket conn, Framedata f ) {
}
/**
@@ -79,12 +82,23 @@ public void onWebsocketPong( IWebSocket conn, Framedata f ) {
* This is specifically implemented for gitime's WebSocket client for Flash:
* http://github.com/gimite/web-socket-js
*
- * @return An XML String that comforms to Flash's security policy. You MUST
+ * @return An XML String that comforts to Flash's security policy. You MUST
* not include the null char at the end, it is appended automatically.
+ * @throws InvalidDataException thrown when some data that is required to generate the flash-policy like the websocket local port could not be obtained e.g because the websocket is not connected.
*/
@Override
- public String getFlashPolicy( IWebSocket conn ) {
- return "\0";
+ public String getFlashPolicy( WebSocket conn ) throws InvalidDataException {
+ InetSocketAddress adr = conn.getLocalSocketAddress();
+ if(null == adr){
+ throw new InvalidHandshakeException( "socket not bound" );
+ }
+
+ StringBuffer sb = new StringBuffer( 90 );
+ sb.append( "\0" );
+
+ return sb.toString();
}
}
diff --git a/src/main/java/org/java_websocket/WebSocketFactory.java b/src/main/java/org/java_websocket/WebSocketFactory.java
index a412a9988..651e97430 100644
--- a/src/main/java/org/java_websocket/WebSocketFactory.java
+++ b/src/main/java/org/java_websocket/WebSocketFactory.java
@@ -6,7 +6,7 @@
import org.java_websocket.drafts.Draft;
public interface WebSocketFactory {
- public IWebSocket createWebSocket( WebSocketAdapter a, Draft d, Socket s );
- public IWebSocket createWebSocket( WebSocketAdapter a, List drafts, Socket s );
+ public WebSocket createWebSocket( WebSocketAdapter a, Draft d, Socket s );
+ public WebSocket createWebSocket( WebSocketAdapter a, List drafts, Socket s );
}
diff --git a/src/main/java/org/java_websocket/WebSocketImpl.java b/src/main/java/org/java_websocket/WebSocketImpl.java
index 44dabccdd..b7adea100 100644
--- a/src/main/java/org/java_websocket/WebSocketImpl.java
+++ b/src/main/java/org/java_websocket/WebSocketImpl.java
@@ -42,7 +42,11 @@
* text frames, and receiving frames through an event-based model.
*
*/
-public class WebSocketImpl extends WebSocket {
+public class WebSocketImpl implements WebSocket {
+
+ public static int RCVBUF = 16384;
+
+ public static/*final*/boolean DEBUG = false; // must be final in the future in order to take advantage of VM optimization
public static final List defaultdraftlist = new ArrayList( 4 );
static {
@@ -54,8 +58,6 @@ public class WebSocketImpl extends WebSocket {
public SelectionKey key;
- /* only used to obtain the socket addresses*/
- public final Socket socket;
/** the possibly wrapped channel object whose selection is controlled by {@link #key} */
public ByteChannel channel;
/**
@@ -91,7 +93,7 @@ public class WebSocketImpl extends WebSocket {
private Opcode current_continuous_frame_opcode = null;
/** the bytes of an incomplete received handshake */
- private ByteBuffer tmpHandshakeBytes;
+ private ByteBuffer tmpHandshakeBytes = ByteBuffer.allocate( 0 );
/** stores the handshake sent by this websocket ( Role.CLIENT only ) */
private ClientHandshake handshakerequest = null;
@@ -99,12 +101,14 @@ public class WebSocketImpl extends WebSocket {
private String closemessage = null;
private Integer closecode = null;
private Boolean closedremotely = null;
+
+ private String resourceDescriptor = null;
/**
* crates a websocket with server role
*/
- public WebSocketImpl( WebSocketListener listener , List drafts , Socket sock ) {
- this( listener, (Draft) null, sock );
+ 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() ) {
@@ -116,44 +120,62 @@ public WebSocketImpl( WebSocketListener listener , List drafts , Socket s
/**
* crates a websocket with client role
+ *
+ * @param socket
+ * may be unbound
*/
- public WebSocketImpl( WebSocketListener listener , Draft draft , Socket sock ) {
+ 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();
- this.socket = sock;
+ }
+
+ @Deprecated
+ public WebSocketImpl( WebSocketListener listener , Draft draft , Socket socket ) {
+ this( listener, draft );
+ }
+
+ @Deprecated
+ public WebSocketImpl( WebSocketListener listener , List drafts , Socket socket ) {
+ this( listener, drafts );
}
/**
*
*/
public void decode( ByteBuffer socketBuffer ) {
- if( !socketBuffer.hasRemaining() || flushandclosestate )
- return;
+ 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( readystate == READYSTATE.OPEN ) {
- decodeFrames( socketBuffer );
+ if( readystate != READYSTATE.NOT_YET_CONNECTED ) {
+ decodeFrames( socketBuffer );;
} else {
if( decodeHandshake( socketBuffer ) ) {
- decodeFrames( socketBuffer );
+ 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 == null ) {
+ if( tmpHandshakeBytes.capacity() == 0 ) {
socketBuffer = socketBufferNew;
} else {
if( tmpHandshakeBytes.remaining() < socketBufferNew.remaining() ) {
@@ -172,8 +194,12 @@ private boolean decodeHandshake( ByteBuffer socketBufferNew ) {
if( draft == null ) {
HandshakeState isflashedgecase = isFlashEdgeCase( socketBuffer );
if( isflashedgecase == HandshakeState.MATCHED ) {
- write( ByteBuffer.wrap( Charsetfunctions.utf8Bytes( wsl.getFlashPolicy( this ) ) ) );
- close( CloseFrame.FLASHPOLICY, "" );
+ try {
+ write( ByteBuffer.wrap( Charsetfunctions.utf8Bytes( wsl.getFlashPolicy( this ) ) ) );
+ close( CloseFrame.FLASHPOLICY, "" );
+ } catch ( InvalidDataException e ) {
+ close( CloseFrame.ABNORMAL_CLOSE, "remote peer closed connection before flashpolicy could be transmitted", true );
+ }
return false;
}
}
@@ -195,6 +221,7 @@ private boolean decodeHandshake( ByteBuffer socketBufferNew ) {
ClientHandshake handshake = (ClientHandshake) tmphandshake;
handshakestate = d.acceptHandshakeAsServer( handshake );
if( handshakestate == HandshakeState.MATCHED ) {
+ resourceDescriptor = handshake.getResourceDescriptor();
ServerHandshakeBuilder response;
try {
response = wsl.onWebsocketHandshakeReceivedAsServer( this, d, handshake );
@@ -241,7 +268,7 @@ private boolean decodeHandshake( ByteBuffer socketBufferNew ) {
draft.setParseMode( role );
Handshakedata tmphandshake = draft.translateHandshake( socketBuffer );
if( tmphandshake instanceof ServerHandshake == false ) {
- flushAndClose( CloseFrame.PROTOCOL_ERROR, "Wwrong http function", false );
+ flushAndClose( CloseFrame.PROTOCOL_ERROR, "wrong http function", false );
return false;
}
ServerHandshake handshake = (ServerHandshake) tmphandshake;
@@ -267,7 +294,7 @@ private boolean decodeHandshake( ByteBuffer socketBufferNew ) {
close( e );
}
} catch ( IncompleteHandshakeException e ) {
- if( tmpHandshakeBytes == null ) {
+ if( tmpHandshakeBytes.capacity() == 0 ) {
socketBuffer.reset();
int newsize = e.getPreferedSize();
if( newsize == 0 ) {
@@ -288,8 +315,6 @@ private boolean decodeHandshake( ByteBuffer socketBufferNew ) {
}
private void decodeFrames( ByteBuffer socketBuffer ) {
- if( flushandclosestate )
- return;
List frames;
try {
@@ -297,8 +322,6 @@ private void decodeFrames( ByteBuffer socketBuffer ) {
for( Framedata f : frames ) {
if( DEBUG )
System.out.println( "matched frame: " + f );
- if( flushandclosestate )
- return;
Opcode curop = f.getOpcode();
boolean fin = f.isFin();
@@ -371,6 +394,8 @@ private void decodeFrames( ByteBuffer socketBuffer ) {
}
private void close( int code, String message, boolean remote ) {
+ if( DEBUG )
+ System.out.println( "close: code: " + code + " message: " + message);
if( readystate != READYSTATE.CLOSING && readystate != READYSTATE.CLOSED ) {
if( readystate == READYSTATE.OPEN ) {
if( code == CloseFrame.ABNORMAL_CLOSE ) {
@@ -411,6 +436,8 @@ private void close( int code, String message, boolean remote ) {
@Override
public void close( int code, String message ) {
+ if( DEBUG )
+ System.out.println( "close: code: " + code + " message: " + message);
close( code, message, false );
}
@@ -424,6 +451,8 @@ public void close( int code, String message ) {
**/
protected synchronized void closeConnection( int code, String message, boolean remote ) {
+ if( DEBUG )
+ System.out.println( "close connection: code: " + code + " message: " + message + " remote: " + remote);
if( readystate == READYSTATE.CLOSED ) {
return;
}
@@ -431,6 +460,8 @@ protected synchronized void closeConnection( int code, String message, boolean r
if( key != null ) {
// key.attach( null ); //see issue #114
key.cancel();
+ }
+ if( channel != null ) {
try {
channel.close();
} catch ( IOException e ) {
@@ -447,7 +478,7 @@ protected synchronized void closeConnection( int code, String message, boolean r
handshakerequest = null;
readystate = READYSTATE.CLOSED;
-
+ this.outQueue.clear();
}
protected void closeConnection( int code, boolean remote ) {
@@ -489,9 +520,8 @@ protected synchronized void flushAndClose( int code, String message, boolean rem
public void eot() {
if( getReadyState() == READYSTATE.NOT_YET_CONNECTED ) {
closeConnection( CloseFrame.NEVER_CONNECTED, true );
- }
- if( draft == null ) {
- closeConnection( CloseFrame.ABNORMAL_CLOSE, true );
+ } else if( flushandclosestate ) {
+ closeConnection( closecode, closemessage, closedremotely );
} else if( draft.getCloseHandshakeType() == CloseHandshakeType.NONE ) {
closeConnection( CloseFrame.NORMAL, true );
} else if( draft.getCloseHandshakeType() == CloseHandshakeType.ONEWAY ) {
@@ -509,7 +539,6 @@ public void close( int code ) {
close( code, "", false );
}
- @Override
public void close( InvalidDataException e ) {
close( e.getCloseCode(), e.getMessage(), false );
}
@@ -553,6 +582,11 @@ private void send( Collection frames ) {
}
}
+ @Override
+ public void sendFragmentedFrame( Opcode op, ByteBuffer buffer, boolean fin ) {
+ send( draft.continuousFrame( op, buffer, fin ) );
+ }
+
@Override
public void sendFrame( Framedata framedata ) {
if( DEBUG )
@@ -589,6 +623,9 @@ public void startHandshake( ClientHandshakeBuilder handshakedata ) throws Invali
// 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 );
@@ -680,12 +717,12 @@ public String toString() {
@Override
public InetSocketAddress getRemoteSocketAddress() {
- return (InetSocketAddress) socket.getRemoteSocketAddress();
+ return wsl.getRemoteSocketAddress( this );
}
@Override
public InetSocketAddress getLocalSocketAddress() {
- return (InetSocketAddress) socket.getLocalSocketAddress();
+ return wsl.getLocalSocketAddress( this );
}
@Override
@@ -693,4 +730,16 @@ public Draft getDraft() {
return draft;
}
+ @Override
+ public void close() {
+ if( DEBUG )
+ System.out.println( "close: CloseFrame.NORMAL" );
+ close( CloseFrame.NORMAL );
+ }
+
+ @Override
+ public String getResourceDescriptor() {
+ return resourceDescriptor;
+ }
+
}
diff --git a/src/main/java/org/java_websocket/WebSocketListener.java b/src/main/java/org/java_websocket/WebSocketListener.java
index 91dafa448..93478d940 100644
--- a/src/main/java/org/java_websocket/WebSocketListener.java
+++ b/src/main/java/org/java_websocket/WebSocketListener.java
@@ -1,5 +1,6 @@
package org.java_websocket;
+import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import org.java_websocket.drafts.Draft;
@@ -32,7 +33,7 @@ public interface WebSocketListener {
* @throws InvalidDataException
* Throwing this exception will cause this handshake to be rejected
*/
- public ServerHandshakeBuilder onWebsocketHandshakeReceivedAsServer( IWebSocket conn, Draft draft, ClientHandshake request ) throws InvalidDataException;
+ public 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
@@ -47,7 +48,7 @@ public interface WebSocketListener {
* @throws InvalidDataException
* Allows the client to reject the connection with the server in respect of its handshake response.
*/
- public void onWebsocketHandshakeReceivedAsClient( IWebSocket conn, ClientHandshake request, ServerHandshake response ) throws InvalidDataException;
+ public 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
@@ -60,7 +61,7 @@ public interface WebSocketListener {
* @throws InvalidDataException
* Allows the client to stop the connection from progressing
*/
- public void onWebsocketHandshakeSentAsClient( IWebSocket conn, ClientHandshake request ) throws InvalidDataException;
+ public void onWebsocketHandshakeSentAsClient( WebSocket conn, ClientHandshake request ) throws InvalidDataException;
/**
* Called when an entire text frame has been received. Do whatever you want
@@ -71,7 +72,7 @@ public interface WebSocketListener {
* @param message
* The UTF-8 decoded message that was received.
*/
- public void onWebsocketMessage( IWebSocket conn, String message );
+ public void onWebsocketMessage( WebSocket conn, String message );
/**
* Called when an entire binary frame has been received. Do whatever you want
@@ -82,9 +83,9 @@ public interface WebSocketListener {
* @param blob
* The binary message that was received.
*/
- public void onWebsocketMessage( IWebSocket conn, ByteBuffer blob );
+ public void onWebsocketMessage( WebSocket conn, ByteBuffer blob );
- public void onWebsocketMessageFragment( IWebSocket conn, Framedata frame );
+ public void onWebsocketMessageFragment( WebSocket conn, Framedata frame );
/**
* Called after onHandshakeReceived returns true.
@@ -94,7 +95,7 @@ public interface WebSocketListener {
* @param conn
* The WebSocket instance this event is occuring on.
*/
- public void onWebsocketOpen( IWebSocket conn, Handshakedata d );
+ public void onWebsocketOpen( WebSocket conn, Handshakedata d );
/**
* Called after WebSocket#close is explicity called, or when the
@@ -103,13 +104,13 @@ public interface WebSocketListener {
* @param conn
* The WebSocket instance this event is occuring on.
*/
- public void onWebsocketClose( IWebSocket ws, int code, String reason, boolean remote );
+ public void onWebsocketClose( WebSocket ws, int code, String reason, boolean remote );
/** called as soon as no further frames are accepted */
- public void onWebsocketClosing( IWebSocket ws, int code, String reason, boolean remote );
+ public void onWebsocketClosing( WebSocket ws, int code, String reason, boolean remote );
/** send when this peer sends a close handshake */
- public void onWebsocketCloseInitiated( IWebSocket ws, int code, String reason );
+ public void onWebsocketCloseInitiated( WebSocket ws, int code, String reason );
/**
* Called if an exception worth noting occurred.
@@ -119,7 +120,7 @@ public interface WebSocketListener {
* 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.
*/
- public void onWebsocketError( IWebSocket conn, Exception ex );
+ public void onWebsocketError( WebSocket conn, Exception ex );
/**
* Called a ping frame has been received.
@@ -128,19 +129,23 @@ public interface WebSocketListener {
* @param f
* The ping frame. Control frames may contain payload.
*/
- public void onWebsocketPing( IWebSocket conn, Framedata f );
+ public void onWebsocketPing( WebSocket conn, Framedata f );
/**
* Called when a pong frame is received.
**/
- public void onWebsocketPong( IWebSocket conn, Framedata f );
+ public void onWebsocketPong( WebSocket conn, Framedata f );
/**
* Gets the XML string that should be returned if a client requests a Flash
* security policy.
+ * @throws InvalidDataException thrown when some data that is required to generate the flash-policy like the websocket local port could not be obtained.
*/
- public String getFlashPolicy( IWebSocket conn );
+ public String getFlashPolicy( WebSocket conn ) throws InvalidDataException;
/** This method is used to inform the selector thread that there is data queued to be written to the socket. */
- public void onWriteDemand( IWebSocket conn );
+ public void onWriteDemand( WebSocket conn );
+
+ public InetSocketAddress getLocalSocketAddress( WebSocket conn );
+ public InetSocketAddress getRemoteSocketAddress( WebSocket conn );
}
diff --git a/src/main/java/org/java_websocket/WrappedByteChannel.java b/src/main/java/org/java_websocket/WrappedByteChannel.java
index b7e110fa8..83a3290b3 100644
--- a/src/main/java/org/java_websocket/WrappedByteChannel.java
+++ b/src/main/java/org/java_websocket/WrappedByteChannel.java
@@ -10,6 +10,17 @@ public interface WrappedByteChannel extends ByteChannel {
public boolean isNeedWrite();
public 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)
+ **/
public 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)}.
+ **/
public int readMore( ByteBuffer dst ) throws SSLException;
+ public boolean isBlocking();
}
diff --git a/src/main/java/org/java_websocket/client/AbstractClientProxyChannel.java b/src/main/java/org/java_websocket/client/AbstractClientProxyChannel.java
new file mode 100644
index 000000000..bbac67258
--- /dev/null
+++ b/src/main/java/org/java_websocket/client/AbstractClientProxyChannel.java
@@ -0,0 +1,38 @@
+package org.java_websocket.client;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.nio.channels.ByteChannel;
+
+import org.java_websocket.AbstractWrappedByteChannel;
+
+public abstract class AbstractClientProxyChannel extends AbstractWrappedByteChannel {
+ protected final ByteBuffer proxyHandshake;
+
+
+ /**
+ * @param towrap
+ * The channel to the proxy server
+ **/
+ public AbstractClientProxyChannel( ByteChannel towrap ) {
+ super( towrap );
+ try {
+ proxyHandshake = ByteBuffer.wrap( buildHandShake().getBytes( "ASCII" ) );
+ } catch ( UnsupportedEncodingException e ) {
+ throw new RuntimeException( e );
+ }
+ }
+
+ @Override
+ public int write( ByteBuffer src ) throws IOException {
+ if( !proxyHandshake.hasRemaining() ) {
+ return super.write( src );
+ } else {
+ return super.write( proxyHandshake );
+ }
+ }
+
+ public abstract String buildHandShake();
+
+}
diff --git a/src/main/java/org/java_websocket/client/DefaultSSLWebSocketClientFactory.java b/src/main/java/org/java_websocket/client/DefaultSSLWebSocketClientFactory.java
deleted file mode 100644
index 011ce655e..000000000
--- a/src/main/java/org/java_websocket/client/DefaultSSLWebSocketClientFactory.java
+++ /dev/null
@@ -1,51 +0,0 @@
-package org.java_websocket.client;
-import java.io.IOException;
-import java.net.Socket;
-import java.nio.channels.ByteChannel;
-import java.nio.channels.SelectionKey;
-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;
-import org.java_websocket.client.WebSocketClient.WebSocketClientFactory;
-import org.java_websocket.drafts.Draft;
-
-
-public class DefaultSSLWebSocketClientFactory implements WebSocketClientFactory {
- protected SSLContext sslcontext;
- protected ExecutorService exec;
-
- public DefaultSSLWebSocketClientFactory( SSLContext sslContext ) {
- this( sslContext, Executors.newSingleThreadScheduledExecutor() );
- }
-
- public DefaultSSLWebSocketClientFactory( SSLContext sslContext , ExecutorService exec ) {
- if( sslContext == null || exec == null )
- throw new IllegalArgumentException();
- this.sslcontext = sslContext;
- this.exec = exec;
- }
-
- @Override
- public ByteChannel wrapChannel( SelectionKey c, String host, int port ) throws IOException {
- SSLEngine e = sslcontext.createSSLEngine( host, port );
- e.setUseClientMode( true );
- return new SSLSocketChannel2( c, e, exec );
- }
-
- @Override
- public WebSocketImpl createWebSocket( WebSocketAdapter a, Draft d, Socket c ) {
- return new WebSocketImpl( a, d, c );
- }
-
- @Override
- public WebSocketImpl createWebSocket( WebSocketAdapter a, List d, Socket s ) {
- return new WebSocketImpl( a, d, s );
- }
-}
\ No newline at end of file
diff --git a/src/main/java/org/java_websocket/client/WebSocketClient.java b/src/main/java/org/java_websocket/client/WebSocketClient.java
index dda308849..2475ffece 100644
--- a/src/main/java/org/java_websocket/client/WebSocketClient.java
+++ b/src/main/java/org/java_websocket/client/WebSocketClient.java
@@ -1,71 +1,57 @@
package org.java_websocket.client;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.net.InetSocketAddress;
+import java.net.Proxy;
import java.net.Socket;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.channels.ByteChannel;
-import java.nio.channels.CancelledKeyException;
-import java.nio.channels.ClosedByInterruptException;
import java.nio.channels.NotYetConnectedException;
import java.nio.channels.SelectionKey;
-import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
-import java.util.Iterator;
import java.util.List;
import java.util.Map;
-import java.util.Set;
import java.util.concurrent.CountDownLatch;
-import org.java_websocket.IWebSocket;
-import org.java_websocket.IWebSocket.READYSTATE;
-import org.java_websocket.SocketChannelIOHelper;
import org.java_websocket.WebSocket;
import org.java_websocket.WebSocketAdapter;
import org.java_websocket.WebSocketFactory;
import org.java_websocket.WebSocketImpl;
-import org.java_websocket.WrappedByteChannel;
import org.java_websocket.drafts.Draft;
-import org.java_websocket.drafts.Draft_10;
+import org.java_websocket.drafts.Draft_17;
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;
/**
- * The WebSocketClient is an abstract class that expects a valid
- * "ws://" URI to connect to. When connected, an instance recieves important
- * events related to the life of the connection. A subclass must implement
- * onOpen, onClose, and onMessage to be
- * useful. An instance can send messages to it's connected server via the
- * send method.
- *
- * @author Nathan Rajlich
+ * 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 WebSocketAdapter implements Runnable {
+public abstract class WebSocketClient extends WebSocketAdapter implements Runnable, WebSocket {
/**
* The URI this channel is supposed to connect to.
*/
- private URI uri = null;
+ protected URI uri = null;
- private WebSocketImpl conn = null;
- /**
- * The SocketChannel instance this channel uses.
- */
- private SocketChannel channel = null;
+ private WebSocketImpl engine = null;
- private ByteChannel wrappedchannel = null;
+ private Socket socket = null;
- private SelectionKey key = null;
- /**
- * The 'Selector' used to get event keys from the underlying socket.
- */
- private Selector selector = null;
+ private InputStream istream;
- private Thread thread;
+ private OutputStream ostream;
+
+ private Proxy proxy = Proxy.NO_PROXY;
+
+ private Thread writeThread;
private Draft draft;
@@ -77,88 +63,77 @@ public abstract class WebSocketClient extends WebSocketAdapter implements Runnab
private int timeout = 0;
- WebSocketClientFactory wf = new WebSocketClientFactory() {
- @Override
- public IWebSocket createWebSocket( WebSocketAdapter a, Draft d, Socket s ) {
- return new WebSocketImpl( WebSocketClient.this, d, s );
- }
-
- @Override
- public IWebSocket createWebSocket( WebSocketAdapter a, List d, Socket s ) {
- return new WebSocketImpl( WebSocketClient.this, d, s );
- }
-
- @Override
- public ByteChannel wrapChannel( SelectionKey c, String host, int port ) {
- return (ByteChannel) c.channel();
- }
- };
+ private int connectTimeout = 0;
+ /** This open a websocket connection as specified by rfc6455 */
public WebSocketClient( URI serverURI ) {
- this( serverURI, new Draft_10() );
+ this( serverURI, new Draft_17() );
}
/**
* Constructs a WebSocketClient instance and sets it to the connect to the
- * specified URI. The channel does not attampt to connect automatically. You
- * must call connect first to initiate the socket connection.
+ * specified URI. The channel does not attampt to connect automatically. The connection
+ * will be established once you call connect.
*/
public WebSocketClient( URI serverUri , Draft draft ) {
this( serverUri, draft, null, 0 );
}
- public WebSocketClient( URI serverUri , Draft draft , Map headers , int connecttimeout ) {
+ public WebSocketClient( URI serverUri , Draft protocolDraft , Map httpHeaders , int connectTimeout ) {
if( serverUri == null ) {
throw new IllegalArgumentException();
- }
- if( draft == null ) {
+ } else if( protocolDraft == null ) {
throw new IllegalArgumentException( "null as draft is permitted for `WebSocketServer` only!" );
}
this.uri = serverUri;
- this.draft = draft;
- this.headers = headers;
- this.timeout = connecttimeout;
+ this.draft = protocolDraft;
+ this.headers = httpHeaders;
+ this.connectTimeout = connectTimeout;
+ this.engine = new WebSocketImpl( this, protocolDraft );
}
/**
- * Gets the URI that this WebSocketClient is connected to.
- *
- * @return The URI for this WebSocketClient.
+ * Returns the URI that this WebSocketClient is connected to.
*/
public URI getURI() {
return uri;
}
- /** Returns the protocol version this channel uses. */
+ /**
+ * Returns the protocol version this channel uses.
+ * For more infos see https://github.com/TooTallNate/Java-WebSocket/wiki/Drafts
+ */
public Draft getDraft() {
return draft;
}
/**
- * Starts a background thread that attempts and maintains a WebSocket
- * connection to the URI specified in the constructor or via setURI.
- * setURI.
+ * Initiates the websocket connection. This method does not block.
*/
public void connect() {
- if( thread != null )
+ if( writeThread != null )
throw new IllegalStateException( "WebSocketClient objects are not reuseable" );
- thread = new Thread( this );
- thread.start();
+ writeThread = new Thread( this );
+ writeThread.start();
}
/**
- * Same as connect but blocks until the websocket connected or failed to do so.
+ * Same as connect but blocks until the websocket connected or failed to do so.
* Returns whether it succeeded or not.
**/
public boolean connectBlocking() throws InterruptedException {
connect();
connectLatch.await();
- return conn.isOpen();
+ 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( thread != null && conn != null ) {
- conn.close( CloseFrame.NORMAL );
+ if( writeThread != null ) {
+ engine.close( CloseFrame.NORMAL );
}
}
@@ -168,134 +143,74 @@ public void closeBlocking() throws InterruptedException {
}
/**
- * Sends text to the connected WebSocket server.
+ * Sends text to the connected websocket server.
*
* @param text
- * The String to send to the WebSocket server.
+ * The string which will be transmitted.
*/
+ @Override
public void send( String text ) throws NotYetConnectedException {
- if( conn != null ) {
- conn.send( text );
- }
+ engine.send( text );
}
/**
- * Sends data to the connected WebSocket server.
+ * Sends binary data to the connected webSocket server.
*
* @param data
- * The Byte-Array of data to send to the WebSocket server.
+ * The byte-Array of data to send to the WebSocket server.
*/
+ @Override
public void send( byte[] data ) throws NotYetConnectedException {
- if( conn != null ) {
- conn.send( data );
- }
- }
-
- private void tryToConnect( InetSocketAddress remote ) throws IOException {
- channel = SocketChannel.open();
- channel.configureBlocking( false );
- channel.connect( remote );
- selector = Selector.open();
- key = channel.register( selector, SelectionKey.OP_CONNECT );
+ engine.send( data );
}
- // Runnable IMPLEMENTATION /////////////////////////////////////////////////
public void run() {
- if( thread == null )
- thread = Thread.currentThread();
- interruptableRun();
-
- assert ( !channel.isOpen() );
-
try {
- if( selector != null ) // if the initialization in tryToConnect fails, it could be null
- selector.close();
- } catch ( IOException e ) {
- onError( e );
+ if( socket == null ) {
+ socket = new Socket( proxy );
+ } else if( socket.isClosed() ) {
+ throw new IOException();
+ }
+ if( !socket.isBound() )
+ socket.connect( new InetSocketAddress( uri.getHost(), getPort() ), connectTimeout );
+ 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;
- private final void interruptableRun() {
try {
- tryToConnect( new InetSocketAddress( uri.getHost(), getPort() ) );
- } catch ( ClosedByInterruptException e ) {
- onWebsocketError( null, e );
- return;
- } catch ( /*IOException | SecurityException | UnresolvedAddressException*/Exception e ) {//
- onWebsocketError( conn, e );
- conn.closeConnection( CloseFrame.NEVER_CONNECTED, e.getMessage() );
- return;
- }
- conn = (WebSocketImpl) wf.createWebSocket( this, draft, channel.socket() );
- ByteBuffer buff = ByteBuffer.allocate( WebSocket.RCVBUF );
- try/*IO*/{
- while ( channel.isOpen() ) {
- SelectionKey key = null;
- selector.select( timeout );
- Set keys = selector.selectedKeys();
- Iterator i = keys.iterator();
- if( conn.getReadyState() == READYSTATE.NOT_YET_CONNECTED && !i.hasNext() ) {
- // Hack for issue #140:
- // Android does simply return form select without closing the channel if address is not reachable(which seems to be a bug in the android nio proivder)
- // TODO provide a way to fix this problem which does not require this hack
- throw new IOException( "Host is not reachable(Android Hack)" );
- }
- while ( i.hasNext() ) {
- key = i.next();
- i.remove();
- if( !key.isValid() ) {
- conn.eot();
- continue;
- }
- if( key.isReadable() && SocketChannelIOHelper.read( buff, this.conn, wrappedchannel ) ) {
- conn.decode( buff );
- }
- if( key.isConnectable() ) {
- try {
- finishConnect( key );
- } catch ( InvalidHandshakeException e ) {
- conn.close( e ); // http error
- }
- }
- if( key.isWritable() ) {
- if( SocketChannelIOHelper.batch( conn, wrappedchannel ) ) {
- if( key.isValid() )
- key.interestOps( SelectionKey.OP_READ );
- } else {
- key.interestOps( SelectionKey.OP_READ | SelectionKey.OP_WRITE );
- }
- }
- }
- if( wrappedchannel instanceof WrappedByteChannel ) {
- WrappedByteChannel w = (WrappedByteChannel) wrappedchannel;
- if( w.isNeedRead() ) {
- while ( SocketChannelIOHelper.read( buff, conn, w ) ) {
- conn.decode( buff );
- }
- }
- }
+ while ( !isClosed() && ( readBytes = istream.read( rawbuffer ) ) != -1 ) {
+ engine.decode( ByteBuffer.wrap( rawbuffer, 0, readBytes ) );
}
-
- } catch ( CancelledKeyException e ) {
- conn.eot();
+ engine.eot();
} catch ( IOException e ) {
- conn.eot();
+ engine.eot();
} catch ( RuntimeException e ) {
// this catch case covers internal errors only and indicates a bug in this websocket implementation
onError( e );
- conn.closeConnection( CloseFrame.ABNORMAL_CLOSE, e.getMessage() );
+ engine.closeConnection( CloseFrame.ABNORMAL_CLOSE, e.getMessage() );
}
+ assert ( socket.isClosed() );
}
-
private int getPort() {
int port = uri.getPort();
if( port == -1 ) {
String scheme = uri.getScheme();
if( scheme.equals( "wss" ) ) {
- return IWebSocket.DEFAULT_WSS_PORT;
+ return WebSocket.DEFAULT_WSS_PORT;
} else if( scheme.equals( "ws" ) ) {
- return IWebSocket.DEFAULT_PORT;
+ return WebSocket.DEFAULT_PORT;
} else {
throw new RuntimeException( "unkonow scheme" + scheme );
}
@@ -303,17 +218,6 @@ private int getPort() {
return port;
}
- private void finishConnect( SelectionKey key ) throws IOException , InvalidHandshakeException {
- if( !channel.finishConnect() )
- return;
- // Now that we're connected, re-register for only 'READ' keys.
- conn.key = key.interestOps( SelectionKey.OP_READ | SelectionKey.OP_WRITE );
-
- conn.channel = wrappedchannel = wf.wrapChannel( key, uri.getHost(), getPort() );
- timeout = 0; // since connect is over
- sendHandshake();
- }
-
private void sendHandshake() throws InvalidHandshakeException {
String path;
String part1 = uri.getPath();
@@ -325,7 +229,7 @@ private void sendHandshake() throws InvalidHandshakeException {
if( part2 != null )
path += "?" + part2;
int port = getPort();
- String host = uri.getHost() + ( port != IWebSocket.DEFAULT_PORT ? ":" + port : "" );
+ String host = uri.getHost() + ( port != WebSocket.DEFAULT_PORT ? ":" + port : "" );
HandshakeImpl1Client handshake = new HandshakeImpl1Client();
handshake.setResourceDescriptor( path );
@@ -335,86 +239,82 @@ private void sendHandshake() throws InvalidHandshakeException {
handshake.put( kv.getKey(), kv.getValue() );
}
}
- conn.startHandshake( handshake );
+ engine.startHandshake( handshake );
}
/**
* This represents the state of the connection.
- * You can use this method instead of
*/
+ @Override
public READYSTATE getReadyState() {
- if( conn == null ) {
- return READYSTATE.NOT_YET_CONNECTED;
- }
- return conn.getReadyState();
+ return engine.getReadyState();
}
/**
* Calls subclass' implementation of onMessage.
- *
- * @param conn
- * @param message
*/
@Override
- public final void onWebsocketMessage( IWebSocket conn, String message ) {
+ public final void onWebsocketMessage( WebSocket conn, String message ) {
onMessage( message );
}
@Override
- public final void onWebsocketMessage( IWebSocket conn, ByteBuffer blob ) {
+ 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.
- *
- * @param conn
*/
@Override
- public final void onWebsocketOpen( IWebSocket conn, Handshakedata handshake ) {
+ public final void onWebsocketOpen( WebSocket conn, Handshakedata handshake ) {
connectLatch.countDown();
onOpen( (ServerHandshake) handshake );
}
/**
* Calls subclass' implementation of onClose.
- *
- * @param conn
*/
@Override
- public final void onWebsocketClose( IWebSocket conn, int code, String reason, boolean remote ) {
+ public final void onWebsocketClose( WebSocket conn, int code, String reason, boolean remote ) {
connectLatch.countDown();
closeLatch.countDown();
+ if( writeThread != null )
+ writeThread.interrupt();
+ try {
+ if( socket != null )
+ socket.close();
+ } catch ( IOException e ) {
+ onWebsocketError( this, e );
+ }
onClose( code, reason, remote );
}
/**
* Calls subclass' implementation of onIOError.
- *
- * @param conn
*/
@Override
- public final void onWebsocketError( IWebSocket conn, Exception ex ) {
+ public final void onWebsocketError( WebSocket conn, Exception ex ) {
onError( ex );
}
@Override
- public final void onWriteDemand( IWebSocket conn ) {
- try {
- key.interestOps( SelectionKey.OP_READ | SelectionKey.OP_WRITE );
- selector.wakeup();
- } catch ( CancelledKeyException e ) {
- // since such an exception/event will also occur on the selector there is no need to do anything herec
- }
+ public final void onWriteDemand( WebSocket conn ) {
+ // nothing to do
}
@Override
- public void onWebsocketCloseInitiated( IWebSocket conn, int code, String reason ) {
+ public void onWebsocketCloseInitiated( WebSocket conn, int code, String reason ) {
onCloseInitiated( code, reason );
}
@Override
- public void onWebsocketClosing( IWebSocket conn, int code, String reason, boolean remote ) {
+ public void onWebsocketClosing( WebSocket conn, int code, String reason, boolean remote ) {
onClosing( code, reason, remote );
}
@@ -424,16 +324,22 @@ public void onCloseInitiated( int code, String reason ) {
public void onClosing( int code, String reason, boolean remote ) {
}
- public IWebSocket getConnection() {
- return conn;
+ public WebSocket getConnection() {
+ return engine;
}
- public final void setWebSocketFactory( WebSocketClientFactory wsf ) {
- this.wf = wsf;
+ @Override
+ public InetSocketAddress getLocalSocketAddress( WebSocket conn ) {
+ if( socket != null )
+ return (InetSocketAddress) socket.getLocalSocketAddress();
+ return null;
}
- public final WebSocketFactory getWebSocketFactory() {
- return wf;
+ @Override
+ public InetSocketAddress getRemoteSocketAddress( WebSocket conn ) {
+ if( socket != null )
+ return (InetSocketAddress) socket.getRemoteSocketAddress();
+ return null;
}
// ABTRACT METHODS /////////////////////////////////////////////////////////
@@ -442,9 +348,117 @@ public final WebSocketFactory getWebSocketFactory() {
public abstract void onClose( int code, String reason, boolean remote );
public abstract void onError( Exception ex );
public void onMessage( ByteBuffer bytes ) {
- };
+ }
+ public void onFragment( Framedata frame ) {
+ }
+
+ private class WebsocketWriteThread implements Runnable {
+ @Override
+ public void run() {
+ Thread.currentThread().setName( "WebsocketWriteThread" );
+ try {
+ while ( !Thread.interrupted() ) {
+ ByteBuffer buffer = engine.outQueue.take();
+ ostream.write( buffer.array(), 0, buffer.limit() );
+ ostream.flush();
+ }
+ } catch ( IOException e ) {
+ engine.eot();
+ } catch ( InterruptedException e ) {
+ // this thread is regularly terminated via an interrupt
+ }
+ }
+ }
+
+ 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.
+ **/
+ 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();
+ }
- public interface WebSocketClientFactory extends WebSocketFactory {
- public ByteChannel wrapChannel( SelectionKey key, String host, int port ) throws IOException;
+ @Override
+ public boolean isClosing() {
+ return engine.isClosing();
+ }
+
+ @Override
+ 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 InetSocketAddress getLocalSocketAddress() {
+ return engine.getLocalSocketAddress();
+ }
+ @Override
+ public InetSocketAddress getRemoteSocketAddress() {
+ return engine.getRemoteSocketAddress();
+ }
+
+ @Override
+ public String getResourceDescriptor() {
+ return uri.getPath();
}
}
diff --git a/src/main/java/org/java_websocket/drafts/Draft.java b/src/main/java/org/java_websocket/drafts/Draft.java
index 8beab07d5..65b34de8f 100644
--- a/src/main/java/org/java_websocket/drafts/Draft.java
+++ b/src/main/java/org/java_websocket/drafts/Draft.java
@@ -6,13 +6,16 @@
import java.util.List;
import java.util.Locale;
-import org.java_websocket.IWebSocket.Role;
+import org.java_websocket.WebSocket.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.CloseFrame;
+import org.java_websocket.framing.FrameBuilder;
import org.java_websocket.framing.Framedata;
+import org.java_websocket.framing.Framedata.Opcode;
+import org.java_websocket.framing.FramedataImpl1;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.handshake.ClientHandshakeBuilder;
import org.java_websocket.handshake.HandshakeBuilder;
@@ -46,6 +49,8 @@ public enum CloseHandshakeType {
/** 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 = '0';
@@ -123,6 +128,32 @@ protected boolean basicAccept( Handshakedata handshakedata ) {
public abstract List createFrames( String text, boolean mask );
+ public List continuousFrame( Opcode op, ByteBuffer buffer, boolean fin ) {
+ if( op != Opcode.BINARY && op != Opcode.TEXT && op != Opcode.TEXT ) {
+ throw new IllegalArgumentException( "Only Opcode.BINARY or Opcode.TEXT are allowed" );
+ }
+
+ if( continuousFrameType != null ) {
+ continuousFrameType = Opcode.CONTINUOUS;
+ } else {
+ continuousFrameType = op;
+ }
+
+ FrameBuilder bui = new FramedataImpl1( continuousFrameType );
+ try {
+ bui.setPayload( buffer );
+ } catch ( InvalidDataException e ) {
+ throw new RuntimeException( e ); // can only happen when one builds close frames(Opcode.Close)
+ }
+ bui.setFin( fin );
+ if( fin ) {
+ continuousFrameType = null;
+ } else {
+ continuousFrameType = op;
+ }
+ return Collections.singletonList( (Framedata) bui );
+ }
+
public abstract void reset();
public List createHandshake( Handshakedata handshakedata, Role ownrole ) {
@@ -189,5 +220,9 @@ public int checkAlloc( int bytecount ) throws LimitExedeedException , InvalidDat
public void setParseMode( Role role ) {
this.role = role;
}
+
+ public Role getRole() {
+ return role;
+ }
}
diff --git a/src/main/java/org/java_websocket/drafts/Draft_10.java b/src/main/java/org/java_websocket/drafts/Draft_10.java
index 6ed86f749..305460a52 100644
--- a/src/main/java/org/java_websocket/drafts/Draft_10.java
+++ b/src/main/java/org/java_websocket/drafts/Draft_10.java
@@ -8,7 +8,7 @@
import java.util.List;
import java.util.Random;
-import org.java_websocket.IWebSocket.Role;
+import org.java_websocket.WebSocket.Role;
import org.java_websocket.exceptions.InvalidDataException;
import org.java_websocket.exceptions.InvalidFrameException;
import org.java_websocket.exceptions.InvalidHandshakeException;
@@ -31,13 +31,12 @@
public class Draft_10 extends Draft {
private class IncompleteException extends Throwable {
-
+
/**
* It's Serializable.
*/
private static final long serialVersionUID = 7330519489840500997L;
-
-
+
private int preferedsize;
public IncompleteException( int preferedsize ) {
this.preferedsize = preferedsize;
@@ -63,7 +62,7 @@ public static int readVersion( Handshakedata handshakedata ) {
private ByteBuffer incompleteframe;
private Framedata fragmentedframe = null;
-
+
private final Random reuseableRandom = new Random();
@Override
@@ -117,7 +116,7 @@ public ByteBuffer createBinaryFrame( Framedata framedata ) {
ByteBuffer maskkey = ByteBuffer.allocate( 4 );
maskkey.putInt( reuseableRandom.nextInt() );
buf.put( maskkey.array() );
- for( int i = 0 ; i < mes.limit() ; i++ ) {
+ for( int i = 0 ; mes.hasRemaining() ; i++ ) {
buf.put( (byte) ( mes.get() ^ maskkey.get( i % 4 ) ) );
}
} else
diff --git a/src/main/java/org/java_websocket/drafts/Draft_75.java b/src/main/java/org/java_websocket/drafts/Draft_75.java
index f6e67f8ab..947a35ece 100644
--- a/src/main/java/org/java_websocket/drafts/Draft_75.java
+++ b/src/main/java/org/java_websocket/drafts/Draft_75.java
@@ -7,7 +7,9 @@
import java.util.Random;
import org.java_websocket.exceptions.InvalidDataException;
+import org.java_websocket.exceptions.InvalidFrameException;
import org.java_websocket.exceptions.InvalidHandshakeException;
+import org.java_websocket.exceptions.LimitExedeedException;
import org.java_websocket.exceptions.NotSendableException;
import org.java_websocket.framing.CloseFrame;
import org.java_websocket.framing.FrameBuilder;
@@ -40,14 +42,13 @@ public class Draft_75 extends Draft {
*/
public static final byte END_OF_FRAME = (byte) 0xFF;
+ /** Is only used to detect protocol violations */
protected boolean readingState = false;
- private boolean inframe = false;
+
protected List readyframes = new LinkedList();
protected ByteBuffer currentFrame;
-
-
+
private final Random reuseableRandom = new Random();
-
@Override
public HandshakeState acceptHandshakeAsClient( ClientHandshake request, ServerHandshake response ) {
@@ -122,15 +123,16 @@ public HandshakeBuilder postProcessHandshakeResponseAsServer( ClientHandshake re
}
protected List translateRegularFrame( ByteBuffer buffer ) throws InvalidDataException {
+
while ( buffer.hasRemaining() ) {
byte newestByte = buffer.get();
if( newestByte == START_OF_FRAME ) { // Beginning of Frame
if( readingState )
- return null;
+ throw new InvalidFrameException( "unexpected START_OF_FRAME" );
readingState = true;
} else if( newestByte == END_OF_FRAME ) { // End of Frame
if( !readingState )
- return null;
+ throw new InvalidFrameException( "unexpected END_OF_FRAME" );
// currentFrame will be null if END_OF_FRAME was send directly after
// START_OF_FRAME, thus we will send 'null' as the sent message.
if( this.currentFrame != null ) {
@@ -138,13 +140,12 @@ protected List translateRegularFrame( ByteBuffer buffer ) throws Inva
FramedataImpl1 curframe = new FramedataImpl1();
curframe.setPayload( currentFrame );
curframe.setFin( true );
- curframe.setOptcode( inframe ? Opcode.CONTINUOUS : Opcode.TEXT );
+ curframe.setOptcode( Opcode.TEXT );
readyframes.add( curframe );
this.currentFrame = null;
buffer.mark();
}
readingState = false;
- inframe = false;
} else if( readingState ) { // Regular frame data, add to current frame buffer //TODO This code is very expensive and slow
if( currentFrame == null ) {
currentFrame = createBuffer();
@@ -156,19 +157,14 @@ protected List translateRegularFrame( ByteBuffer buffer ) throws Inva
return null;
}
}
- if( readingState ) {
- FramedataImpl1 curframe = new FramedataImpl1();
- currentFrame.flip();
- curframe.setPayload( currentFrame );
- curframe.setFin( false );
- curframe.setOptcode( inframe ? Opcode.CONTINUOUS : Opcode.TEXT );
- inframe = true;
- readyframes.add( curframe );
- }
+
+ // if no error occurred this block will be reached
+ /*if( readingState ) {
+ checkAlloc(currentFrame.position()+1);
+ }*/
List frames = readyframes;
readyframes = new LinkedList();
- this.currentFrame = null;
return frames;
}
@@ -196,9 +192,9 @@ public ByteBuffer createBuffer() {
return ByteBuffer.allocate( INITIAL_FAMESIZE );
}
- public ByteBuffer increaseBuffer( ByteBuffer full ) {
+ public ByteBuffer increaseBuffer( ByteBuffer full ) throws LimitExedeedException , InvalidDataException {
full.flip();
- ByteBuffer newbuffer = ByteBuffer.allocate( full.capacity() * 2 );
+ ByteBuffer newbuffer = ByteBuffer.allocate( checkAlloc( full.capacity() * 2 ) );
newbuffer.put( full );
return newbuffer;
}
diff --git a/src/main/java/org/java_websocket/drafts/Draft_76.java b/src/main/java/org/java_websocket/drafts/Draft_76.java
index 6d9097573..26f23531e 100644
--- a/src/main/java/org/java_websocket/drafts/Draft_76.java
+++ b/src/main/java/org/java_websocket/drafts/Draft_76.java
@@ -9,7 +9,7 @@
import java.util.List;
import java.util.Random;
-import org.java_websocket.IWebSocket.Role;
+import org.java_websocket.WebSocket.Role;
import org.java_websocket.exceptions.IncompleteHandshakeException;
import org.java_websocket.exceptions.InvalidDataException;
import org.java_websocket.exceptions.InvalidFrameException;
diff --git a/src/main/java/org/java_websocket/framing/CloseFrameBuilder.java b/src/main/java/org/java_websocket/framing/CloseFrameBuilder.java
index 3922cabfa..fee1b540d 100644
--- a/src/main/java/org/java_websocket/framing/CloseFrameBuilder.java
+++ b/src/main/java/org/java_websocket/framing/CloseFrameBuilder.java
@@ -40,7 +40,7 @@ private void setCodeAndMessage( int code, String m ) throws InvalidDataException
m = "";
}
if( code == CloseFrame.NOCODE ) {
- if( !m.isEmpty() ) {
+ if( 0 < m.length() ) {
throw new InvalidDataException( PROTOCOL_ERROR, "A close frame must have a closecode if it has a reason" );
}
return;// empty payload
diff --git a/src/main/java/org/java_websocket/framing/FramedataImpl1.java b/src/main/java/org/java_websocket/framing/FramedataImpl1.java
index 5a1a5574f..5fba075b4 100644
--- a/src/main/java/org/java_websocket/framing/FramedataImpl1.java
+++ b/src/main/java/org/java_websocket/framing/FramedataImpl1.java
@@ -104,7 +104,7 @@ public void append( Framedata nextframe ) throws InvalidFrameException {
@Override
public String toString() {
- return "Framedata{ optcode:" + getOpcode() + ", fin:" + isFin() + ", payloadlength:" + unmaskedpayload.limit() + ", payload:" + Arrays.toString( Charsetfunctions.utf8Bytes( new String( unmaskedpayload.array() ) ) ) + "}";
+ return "Framedata{ optcode:" + getOpcode() + ", fin:" + isFin() + ", payloadlength:[pos:" + unmaskedpayload.position() + ", len:" + unmaskedpayload.remaining() + "], payload:" + Arrays.toString( Charsetfunctions.utf8Bytes( new String( unmaskedpayload.array() ) ) ) + "}";
}
}
diff --git a/src/main/java/org/java_websocket/handshake/ClientHandshake.java b/src/main/java/org/java_websocket/handshake/ClientHandshake.java
index 4ab28475e..918d22184 100644
--- a/src/main/java/org/java_websocket/handshake/ClientHandshake.java
+++ b/src/main/java/org/java_websocket/handshake/ClientHandshake.java
@@ -1,5 +1,6 @@
package org.java_websocket.handshake;
public interface ClientHandshake extends Handshakedata {
+ /**returns the HTTP Request-URI as defined by http://tools.ietf.org/html/rfc2616#section-5.1.2*/
public 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 07d17309b..88ac4f27b 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 @@
package org.java_websocket.handshake;
public interface ClientHandshakeBuilder extends HandshakeBuilder, ClientHandshake {
- public void setResourceDescriptor( String resourcedescriptor );
+ public void setResourceDescriptor( String resourceDescriptor );
}
diff --git a/src/main/java/org/java_websocket/handshake/HandshakeImpl1Client.java b/src/main/java/org/java_websocket/handshake/HandshakeImpl1Client.java
index 789cb087a..15715e37e 100644
--- a/src/main/java/org/java_websocket/handshake/HandshakeImpl1Client.java
+++ b/src/main/java/org/java_websocket/handshake/HandshakeImpl1Client.java
@@ -1,16 +1,18 @@
package org.java_websocket.handshake;
public class HandshakeImpl1Client extends HandshakedataImpl1 implements ClientHandshakeBuilder {
- private String resourcedescriptor;
+ private String resourceDescriptor = "*";
public HandshakeImpl1Client() {
}
- public void setResourceDescriptor( String resourcedescriptor ) throws IllegalArgumentException {
- this.resourcedescriptor = resourcedescriptor;
+ public void setResourceDescriptor( String resourceDescriptor ) throws IllegalArgumentException {
+ if(resourceDescriptor==null)
+ throw new IllegalArgumentException( "http resource descriptor must not be null" );
+ this.resourceDescriptor = resourceDescriptor;
}
public String getResourceDescriptor() {
- return resourcedescriptor;
+ return resourceDescriptor;
}
}
diff --git a/src/main/java/org/java_websocket/handshake/HandshakedataImpl1.java b/src/main/java/org/java_websocket/handshake/HandshakedataImpl1.java
index 39e7d4c9c..d4d9555c6 100644
--- a/src/main/java/org/java_websocket/handshake/HandshakedataImpl1.java
+++ b/src/main/java/org/java_websocket/handshake/HandshakedataImpl1.java
@@ -2,15 +2,14 @@
import java.util.Collections;
import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.Locale;
+import java.util.TreeMap;
public class HandshakedataImpl1 implements HandshakeBuilder {
private byte[] content;
- private LinkedHashMap map;
+ private TreeMap map;
public HandshakedataImpl1() {
- map = new LinkedHashMap();
+ map = new TreeMap( String.CASE_INSENSITIVE_ORDER );
}
/*public HandshakedataImpl1( Handshakedata h ) {
@@ -32,8 +31,8 @@ public Iterator iterateHttpFields() {
@Override
public String getFieldValue( String name ) {
- String s = map.get( name.toLowerCase( Locale.ENGLISH ) );
- if( s == null ) {
+ String s = map.get( name );
+ if ( s == null ) {
return "";
}
return s;
@@ -51,11 +50,11 @@ public void setContent( byte[] content ) {
@Override
public void put( String name, String value ) {
- map.put( name.toLowerCase( Locale.ENGLISH ), value );
+ map.put( name, value );
}
@Override
public boolean hasFieldValue( String name ) {
- return map.containsKey( name.toLowerCase( Locale.ENGLISH ) );
+ return map.containsKey( name );
}
}
diff --git a/src/main/java/org/java_websocket/server/DefaultSSLWebSocketServerFactory.java b/src/main/java/org/java_websocket/server/DefaultSSLWebSocketServerFactory.java
index 6841151fc..b871260f8 100644
--- a/src/main/java/org/java_websocket/server/DefaultSSLWebSocketServerFactory.java
+++ b/src/main/java/org/java_websocket/server/DefaultSSLWebSocketServerFactory.java
@@ -3,6 +3,7 @@
import java.net.Socket;
import java.nio.channels.ByteChannel;
import java.nio.channels.SelectionKey;
+import java.nio.channels.SocketChannel;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -32,19 +33,19 @@ public DefaultSSLWebSocketServerFactory( SSLContext sslContext , ExecutorService
}
@Override
- public ByteChannel wrapChannel( SelectionKey c ) throws IOException {
+ public ByteChannel wrapChannel( SocketChannel channel, SelectionKey key ) throws IOException {
SSLEngine e = sslcontext.createSSLEngine();
e.setUseClientMode( false );
- return new SSLSocketChannel2( c, e, exec );
+ return new SSLSocketChannel2( channel, e, exec, key );
}
@Override
public WebSocketImpl createWebSocket( WebSocketAdapter a, Draft d, Socket c ) {
- return new WebSocketImpl( a, d, c );
+ return new WebSocketImpl( a, d );
}
@Override
public WebSocketImpl createWebSocket( WebSocketAdapter a, List d, Socket s ) {
- return new WebSocketImpl( a, d, s );
+ return new WebSocketImpl( a, d );
}
}
\ 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 70df6bc73..3b89cdc2f 100644
--- a/src/main/java/org/java_websocket/server/DefaultWebSocketServerFactory.java
+++ b/src/main/java/org/java_websocket/server/DefaultWebSocketServerFactory.java
@@ -13,14 +13,14 @@
public class DefaultWebSocketServerFactory implements WebSocketServerFactory {
@Override
public WebSocketImpl createWebSocket( WebSocketAdapter a, Draft d, Socket s ) {
- return new WebSocketImpl( a, d, s );
+ return new WebSocketImpl( a, d );
}
@Override
public WebSocketImpl createWebSocket( WebSocketAdapter a, List d, Socket s ) {
- return new WebSocketImpl( a, d, s );
+ return new WebSocketImpl( a, d );
}
@Override
- public SocketChannel wrapChannel( SelectionKey c ) {
- return (SocketChannel) c.channel();
+ public SocketChannel wrapChannel( SocketChannel channel, SelectionKey key ) {
+ return (SocketChannel) channel;
}
}
\ 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 f9ed780bf..2ad5e4169 100644
--- a/src/main/java/org/java_websocket/server/WebSocketServer.java
+++ b/src/main/java/org/java_websocket/server/WebSocketServer.java
@@ -8,6 +8,8 @@
import java.nio.ByteBuffer;
import java.nio.channels.ByteChannel;
import java.nio.channels.CancelledKeyException;
+import java.nio.channels.ClosedByInterruptException;
+import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
@@ -26,7 +28,7 @@
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
-import org.java_websocket.IWebSocket;
+import org.java_websocket.WebSocket;
import org.java_websocket.SocketChannelIOHelper;
import org.java_websocket.WebSocket;
import org.java_websocket.WebSocketAdapter;
@@ -34,9 +36,12 @@
import org.java_websocket.WebSocketImpl;
import org.java_websocket.WrappedByteChannel;
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.handshake.ClientHandshake;
import org.java_websocket.handshake.Handshakedata;
+import org.java_websocket.handshake.ServerHandshakeBuilder;
/**
* WebSocketServer is an abstract class that only takes care of the
@@ -52,7 +57,7 @@ public abstract class WebSocketServer extends WebSocketAdapter implements Runnab
* 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;
+ private final Collection connections;
/**
* The port number that this WebSocket server should listen on. Default is
* WebSocket.DEFAULT_PORT.
@@ -77,7 +82,6 @@ public abstract class WebSocketServer extends WebSocketAdapter implements Runnab
private List decoders;
- private BlockingQueue oqueue;
private List iqueue;
private BlockingQueue buffers;
private int queueinvokes = 0;
@@ -92,7 +96,7 @@ public abstract class WebSocketServer extends WebSocketAdapter implements Runnab
* @see #WebSocketServer(InetSocketAddress, int, List, Collection) more details here
*/
public WebSocketServer() throws UnknownHostException {
- this( new InetSocketAddress( IWebSocket.DEFAULT_PORT ), DECODERS, null );
+ this( new InetSocketAddress( WebSocket.DEFAULT_PORT ), DECODERS, null );
}
/**
@@ -122,7 +126,7 @@ public WebSocketServer( InetSocketAddress address , List drafts ) {
* @see #WebSocketServer(InetSocketAddress, int, List, Collection) more details here
*/
public WebSocketServer( InetSocketAddress address , int decodercount , List drafts ) {
- this( address, decodercount, drafts, new HashSet() );
+ this( address, decodercount, drafts, new HashSet() );
}
/**
@@ -145,7 +149,7 @@ public WebSocketServer( InetSocketAddress address , int decodercount , List more about drafts
*/
- public WebSocketServer( InetSocketAddress address , int decodercount , List drafts , Collection connectionscontainer ) {
+ 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" );
}
@@ -158,7 +162,6 @@ public WebSocketServer( InetSocketAddress address , int decodercount , List();
iqueue = new LinkedList();
decoders = new ArrayList( decodercount );
@@ -193,41 +196,42 @@ public void start() {
* If this method is called before the server is started it will never start.
*
* @param timeout
- * Specifies how many milliseconds shall pass between initiating the close handshakes with the connected clients and closing the servers socket channel.
+ * Specifies how many milliseconds the overall close handshaking may take altogether before the connections are closed without proper close handshaking.
*
* @throws IOException
* When {@link ServerSocketChannel}.close throws an IOException
* @throws InterruptedException
*/
- public void stop( int timeout ) throws IOException , InterruptedException {
- if( !isclosed.compareAndSet( false, true ) ) {
+ 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 = null;
+
+ // copy the connections in a list (prevent callback deadlocks)
synchronized ( connections ) {
- for( IWebSocket ws : connections ) {
- ws.close( CloseFrame.GOING_AWAY );
- }
+ socketsToClose = new ArrayList( connections );
+ }
+
+ for( WebSocket ws : socketsToClose ) {
+ ws.close( CloseFrame.GOING_AWAY );
}
+
synchronized ( this ) {
if( selectorthread != null ) {
if( Thread.currentThread() != selectorthread ) {
}
- selectorthread.interrupt();
- selectorthread.join();
- }
- if( decoders != null ) {
- for( WebSocketWorker w : decoders ) {
- w.interrupt();
+ if( selectorthread != Thread.currentThread() ) {
+ if( socketsToClose.size() > 0 )
+ selectorthread.join( timeout );// isclosed will tell the selectorthread to go down after the last connection was closed
+ selectorthread.interrupt();// in case the selectorthread did not terminate in time we send the interrupt
+ selectorthread.join();
}
}
- if( server != null ) {
- server.close();
- }
}
}
-
public void stop() throws IOException , InterruptedException {
stop( 0 );
}
@@ -239,7 +243,7 @@ public void stop() throws IOException , InterruptedException {
*
* @return The currently connected clients.
*/
- public Collection connections() {
+ public Collection connections() {
return this.connections;
}
@@ -279,12 +283,12 @@ public void run() {
server = ServerSocketChannel.open();
server.configureBlocking( false );
ServerSocket socket = server.socket();
- socket.setReceiveBufferSize( WebSocket.RCVBUF );
+ socket.setReceiveBufferSize( WebSocketImpl.RCVBUF );
socket.bind( address );
selector = Selector.open();
server.register( selector, server.validOps() );
} catch ( IOException ex ) {
- onWebsocketError( null, ex );
+ handleFatal( null, ex );
return;
}
try {
@@ -293,8 +297,6 @@ public void run() {
WebSocketImpl conn = null;
try {
selector.select();
- registerWrite();
-
Set keys = selector.selectedKeys();
Iterator i = keys.iterator();
@@ -316,7 +318,7 @@ public void run() {
channel.configureBlocking( false );
WebSocketImpl w = wsf.createWebSocket( this, drafts, channel.socket() );
w.key = channel.register( selector, SelectionKey.OP_READ, w );
- w.channel = wsf.wrapChannel( w.key );
+ w.channel = wsf.wrapChannel( channel, w.key );
i.remove();
allocateBuffers( w );
continue;
@@ -326,29 +328,29 @@ public void run() {
conn = (WebSocketImpl) key.attachment();
ByteBuffer buf = takeBuffer();
try {
- if( SocketChannelIOHelper.read( buf, conn, (ByteChannel) conn.channel ) ) {
- conn.inQueue.put( buf );
- queue( conn );
- i.remove();
- if( conn.channel instanceof WrappedByteChannel ) {
- if( ( (WrappedByteChannel) conn.channel ).isNeedRead() ) {
- iqueue.add( conn );
+ 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;
- } catch ( RuntimeException e ) {
- pushBuffer( buf );
- throw e;
}
}
if( key.isWritable() ) {
conn = (WebSocketImpl) key.attachment();
- if( SocketChannelIOHelper.batch( conn, (ByteChannel) conn.channel ) ) {
+ if( SocketChannelIOHelper.batch( conn, conn.channel ) ) {
if( key.isValid() )
key.interestOps( SelectionKey.OP_READ );
}
@@ -358,38 +360,53 @@ public void run() {
conn = iqueue.remove( 0 );
WrappedByteChannel c = ( (WrappedByteChannel) conn.channel );
ByteBuffer buf = takeBuffer();
- // JBW/GW - 23DEC12: We get IOExceptions from SSL/TLS and then the buffer pool runs dry since we don't push back.
try {
if( SocketChannelIOHelper.readMore( buf, conn, c ) )
iqueue.add( conn );
- conn.inQueue.put( buf );
- queue( conn );
- } catch (IOException e) {
- pushBuffer(buf);
- throw e;
- } catch (RuntimeException e) {
- pushBuffer(buf);
+ 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( conn, ex );
+ handleIOException( key, conn, ex );
} catch ( InterruptedException e ) {
- return;// FIXME controlled shutdown
+ return;// FIXME controlled shutdown (e.g. take care of buffermanagement)
}
}
} catch ( RuntimeException e ) {
// should hopefully never occur
handleFatal( null, e );
+ } finally {
+ if( decoders != null ) {
+ for( WebSocketWorker w : decoders ) {
+ w.interrupt();
+ }
+ }
+ if( server != null ) {
+ try {
+ server.close();
+ } catch ( IOException e ) {
+ onError( null, e );
+ }
+ }
}
}
-
- protected void allocateBuffers( IWebSocket c ) throws InterruptedException {
+ protected void allocateBuffers( WebSocket c ) throws InterruptedException {
if( queuesize.get() >= 2 * decoders.size() + 1 ) {
return;
}
@@ -397,13 +414,13 @@ protected void allocateBuffers( IWebSocket c ) throws InterruptedException {
buffers.put( createBuffer() );
}
- protected void releaseBuffers( IWebSocket c ) throws InterruptedException {
+ protected void releaseBuffers( WebSocket c ) throws InterruptedException {
// queuesize.decrementAndGet();
// takeBuffer();
}
public ByteBuffer createBuffer() {
- return ByteBuffer.allocate( WebSocket.RCVBUF );
+ return ByteBuffer.allocate( WebSocketImpl.RCVBUF );
}
private void queue( WebSocketImpl ws ) throws InterruptedException {
@@ -424,22 +441,25 @@ private void pushBuffer( ByteBuffer buf ) throws InterruptedException {
buffers.put( buf );
}
- private void registerWrite() throws CancelledKeyException {
- int size = oqueue.size();
- for( int i = 0 ; i < size ; i++ ) {
- WebSocketImpl conn = oqueue.remove();
- conn.key.interestOps( SelectionKey.OP_READ | SelectionKey.OP_WRITE );
- }
- }
-
- private void handleIOException( IWebSocket conn, IOException ex ) {
- onWebsocketError( conn, ex );// conn may be null here
+ 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( IWebSocket conn, RuntimeException e ) {
+ private void handleFatal( WebSocket conn, Exception e ) {
onError( conn, e );
try {
stop();
@@ -469,25 +489,30 @@ protected String getFlashSecurityPolicy() {
}
@Override
- public final void onWebsocketMessage( IWebSocket conn, String message ) {
+ public final void onWebsocketMessage( WebSocket conn, String message ) {
onMessage( conn, message );
}
@Override
- public final void onWebsocketMessage( IWebSocket conn, ByteBuffer blob ) {
+ @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( IWebSocket conn, Handshakedata handshake ) {
+ public final void onWebsocketOpen( WebSocket conn, Handshakedata handshake ) {
if( addConnection( conn ) ) {
onOpen( conn, (ClientHandshake) handshake );
}
}
@Override
- public final void onWebsocketClose( IWebSocket conn, int code, String reason, boolean remote ) {
- oqueue.add( (WebSocketImpl) conn );// because the ostream will close the channel
+ public final void onWebsocketClose( WebSocket conn, int code, String reason, boolean remote ) {
selector.wakeup();
try {
if( removeConnection( conn ) ) {
@@ -509,50 +534,72 @@ public final void onWebsocketClose( IWebSocket conn, int code, String reason, bo
* {@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.
**/
- protected boolean removeConnection( IWebSocket ws ) {
+ protected boolean removeConnection( WebSocket ws ) {
+ boolean removed;
synchronized ( connections ) {
- return this.connections.remove( ws );
+ removed = this.connections.remove( ws );
+ assert ( removed );
+ }
+ 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) */
- protected boolean addConnection( IWebSocket ws ) {
- synchronized ( connections ) {
- return this.connections.add( ws );
+ 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
}
}
-
/**
* @param conn
* may be null if the error does not belong to a single connection
*/
@Override
- public final void onWebsocketError( IWebSocket conn, Exception ex ) {
+ public final void onWebsocketError( WebSocket conn, Exception ex ) {
onError( conn, ex );
}
@Override
- public final void onWriteDemand( IWebSocket w ) {
+ public final void onWriteDemand( WebSocket w ) {
WebSocketImpl conn = (WebSocketImpl) w;
- oqueue.add( conn );
+ 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( IWebSocket conn, int code, String reason ) {
+ public void onWebsocketCloseInitiated( WebSocket conn, int code, String reason ) {
onCloseInitiated( conn, code, reason );
}
@Override
- public void onWebsocketClosing( IWebSocket conn, int code, String reason, boolean remote ) {
+ public void onWebsocketClosing( WebSocket conn, int code, String reason, boolean remote ) {
onClosing( conn, code, reason, remote );
}
- public void onCloseInitiated( IWebSocket conn, int code, String reason ) {
+ public void onCloseInitiated( WebSocket conn, int code, String reason ) {
}
- public void onClosing( IWebSocket conn, int code, String reason, boolean remote ) {
+ public void onClosing( WebSocket conn, int code, String reason, boolean remote ) {
}
@@ -574,8 +621,23 @@ protected boolean onConnect( SelectionKey key ) {
return true;
}
+ 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. */
- public abstract void onOpen( IWebSocket conn, ClientHandshake handshake );
+ public abstract void onOpen( WebSocket conn, ClientHandshake handshake );
/**
* Called after the websocket connection has been closed.
*
@@ -586,13 +648,13 @@ protected boolean onConnect( SelectionKey key ) {
* @param remote
* Returns whether or not the closing of the connection was initiated by the remote host.
**/
- public abstract void onClose( IWebSocket conn, int code, String reason, boolean remote );
+ 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)
**/
- public abstract void onMessage( IWebSocket conn, String message );
+ 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.
@@ -601,14 +663,20 @@ protected boolean onConnect( SelectionKey key ) {
* @param con
* Can be null if there error does not belong to one specific websocket. For example if the servers port could not be bound.
**/
- public abstract void onError( IWebSocket conn, Exception ex );
+ public abstract void onError( WebSocket conn, Exception ex );
/**
* Callback for binary messages received from the remote host
*
* @see #onMessage(WebSocket, String)
**/
- public void onMessage( IWebSocket conn, ByteBuffer message ) {
- };
+ public void onMessage( WebSocket conn, ByteBuffer message ) {
+ }
+
+ /**
+ * @see WebSocket#sendFragmentedFrame(org.java_websocket.framing.Framedata.Opcode, ByteBuffer, boolean)
+ */
+ public void onFragment( WebSocket conn, Framedata fragment ) {
+ }
public class WebSocketWorker extends Thread {
@@ -664,6 +732,6 @@ public interface WebSocketServerFactory extends WebSocketFactory {
* a SelectionKey of an open SocketChannel.
* @return The channel on which the read and write operations will be performed.
*/
- public ByteChannel wrapChannel( SelectionKey key ) throws IOException;
+ public ByteChannel wrapChannel( SocketChannel channel, SelectionKey key ) throws IOException;
}
}
diff --git a/src/test/java/AutobahnClientTest.java b/src/test/java/AutobahnClientTest.java
index bd345186a..a567fd1b4 100644
--- a/src/test/java/AutobahnClientTest.java
+++ b/src/test/java/AutobahnClientTest.java
@@ -5,6 +5,7 @@
import java.nio.ByteBuffer;
import org.java_websocket.WebSocket;
+import org.java_websocket.WebSocketImpl;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.drafts.Draft;
import org.java_websocket.drafts.Draft_17;
@@ -55,11 +56,11 @@ public static void main( String[] args ) {
if( nextline != null ) {
line = nextline;
nextline = null;
- WebSocket.DEBUG = false;
+ WebSocketImpl.DEBUG = false;
} else {
System.out.print( ">" );
line = sysin.readLine();
- WebSocket.DEBUG = true;
+ WebSocketImpl.DEBUG = true;
}
if( line.equals( "l" ) ) {
line = perviousline;
@@ -84,7 +85,7 @@ public static void main( String[] args ) {
uri = URI.create( serverlocation + "/runCase?case=" + spl[ 1 ] + "&agent=" + clientname );
} else if( line.startsWith( "u" ) ) {
- WebSocket.DEBUG = false;
+ WebSocketImpl.DEBUG = false;
uri = URI.create( serverlocation + "/updateReports?agent=" + clientname );
} else if( line.startsWith( "d" ) ) {
try {
diff --git a/src/test/java/AutobahnServerTest.java b/src/test/java/AutobahnServerTest.java
index dcf707b4c..ed7d05c47 100644
--- a/src/test/java/AutobahnServerTest.java
+++ b/src/test/java/AutobahnServerTest.java
@@ -4,6 +4,7 @@
import java.util.Collections;
import org.java_websocket.WebSocket;
+import org.java_websocket.WebSocketImpl;
import org.java_websocket.drafts.Draft;
import org.java_websocket.drafts.Draft_17;
import org.java_websocket.framing.FrameBuilder;
@@ -57,7 +58,7 @@ public void onWebsocketMessageFragment( WebSocket conn, Framedata frame ) {
}
public static void main( String[] args ) throws UnknownHostException {
- WebSocket.DEBUG = false;
+ WebSocketImpl.DEBUG = false;
int port;
try {
port = new Integer( args[ 0 ] );