Browse Source

socketio

10.0
richie 6 years ago
parent
commit
30efd7f5f3
  1. 6
      build.third_step0.gradle
  2. 12
      build.third_step1.gradle
  3. 12
      build.third_step2.gradle
  4. 12
      build.third_step3.gradle
  5. 12
      build.third_step4.gradle
  6. 12
      build.third_step5.gradle
  7. 17
      build.third_step6.gradle
  8. BIN
      fine-socketio/lib/netty-all-4.1.22.Final.jar
  9. 91
      fine-socketio/src/com/fr/third/socketio/AckCallback.java
  10. 39
      fine-socketio/src/com/fr/third/socketio/AckMode.java
  11. 90
      fine-socketio/src/com/fr/third/socketio/AckRequest.java
  12. 28
      fine-socketio/src/com/fr/third/socketio/AuthorizationListener.java
  13. 82
      fine-socketio/src/com/fr/third/socketio/BroadcastAckCallback.java
  14. 137
      fine-socketio/src/com/fr/third/socketio/BroadcastOperations.java
  15. 49
      fine-socketio/src/com/fr/third/socketio/ClientOperations.java
  16. 574
      fine-socketio/src/com/fr/third/socketio/Configuration.java
  17. 26
      fine-socketio/src/com/fr/third/socketio/Disconnectable.java
  18. 20
      fine-socketio/src/com/fr/third/socketio/DisconnectableHub.java
  19. 122
      fine-socketio/src/com/fr/third/socketio/HandshakeData.java
  20. 87
      fine-socketio/src/com/fr/third/socketio/JsonSupportWrapper.java
  21. 35
      fine-socketio/src/com/fr/third/socketio/MultiTypeAckCallback.java
  22. 69
      fine-socketio/src/com/fr/third/socketio/MultiTypeArgs.java
  23. 89
      fine-socketio/src/com/fr/third/socketio/SocketConfig.java
  24. 236
      fine-socketio/src/com/fr/third/socketio/SocketIOChannelInitializer.java
  25. 112
      fine-socketio/src/com/fr/third/socketio/SocketIOClient.java
  26. 50
      fine-socketio/src/com/fr/third/socketio/SocketIONamespace.java
  27. 269
      fine-socketio/src/com/fr/third/socketio/SocketIOServer.java
  28. 45
      fine-socketio/src/com/fr/third/socketio/Transport.java
  29. 39
      fine-socketio/src/com/fr/third/socketio/VoidAckCallback.java
  30. 186
      fine-socketio/src/com/fr/third/socketio/ack/AckManager.java
  31. 57
      fine-socketio/src/com/fr/third/socketio/ack/AckSchedulerKey.java
  32. 31
      fine-socketio/src/com/fr/third/socketio/annotation/AnnotationScanner.java
  33. 36
      fine-socketio/src/com/fr/third/socketio/annotation/OnConnect.java
  34. 66
      fine-socketio/src/com/fr/third/socketio/annotation/OnConnectScanner.java
  35. 35
      fine-socketio/src/com/fr/third/socketio/annotation/OnDisconnect.java
  36. 66
      fine-socketio/src/com/fr/third/socketio/annotation/OnDisconnectScanner.java
  37. 45
      fine-socketio/src/com/fr/third/socketio/annotation/OnEvent.java
  38. 154
      fine-socketio/src/com/fr/third/socketio/annotation/OnEventScanner.java
  39. 105
      fine-socketio/src/com/fr/third/socketio/annotation/ScannerEngine.java
  40. 88
      fine-socketio/src/com/fr/third/socketio/annotation/SpringAnnotationScanner.java
  41. 256
      fine-socketio/src/com/fr/third/socketio/handler/AuthorizeHandler.java
  42. 266
      fine-socketio/src/com/fr/third/socketio/handler/ClientHead.java
  43. 65
      fine-socketio/src/com/fr/third/socketio/handler/ClientsBox.java
  44. 346
      fine-socketio/src/com/fr/third/socketio/handler/EncoderHandler.java
  45. 107
      fine-socketio/src/com/fr/third/socketio/handler/InPacketHandler.java
  46. 120
      fine-socketio/src/com/fr/third/socketio/handler/PacketListener.java
  47. 34
      fine-socketio/src/com/fr/third/socketio/handler/SocketIOException.java
  48. 28
      fine-socketio/src/com/fr/third/socketio/handler/SuccessAuthorizationListener.java
  49. 48
      fine-socketio/src/com/fr/third/socketio/handler/TransportState.java
  50. 57
      fine-socketio/src/com/fr/third/socketio/handler/WrongUrlHandler.java
  51. 36
      fine-socketio/src/com/fr/third/socketio/listener/ClientListeners.java
  52. 24
      fine-socketio/src/com/fr/third/socketio/listener/ConnectListener.java
  53. 34
      fine-socketio/src/com/fr/third/socketio/listener/DataListener.java
  54. 56
      fine-socketio/src/com/fr/third/socketio/listener/DefaultExceptionListener.java
  55. 24
      fine-socketio/src/com/fr/third/socketio/listener/DisconnectListener.java
  56. 35
      fine-socketio/src/com/fr/third/socketio/listener/ExceptionListener.java
  57. 47
      fine-socketio/src/com/fr/third/socketio/listener/ExceptionListenerAdapter.java
  58. 26
      fine-socketio/src/com/fr/third/socketio/listener/MultiTypeEventListener.java
  59. 24
      fine-socketio/src/com/fr/third/socketio/listener/PingListener.java
  60. 33
      fine-socketio/src/com/fr/third/socketio/messages/HttpErrorMessage.java
  61. 38
      fine-socketio/src/com/fr/third/socketio/messages/HttpMessage.java
  62. 41
      fine-socketio/src/com/fr/third/socketio/messages/OutPacketMessage.java
  63. 47
      fine-socketio/src/com/fr/third/socketio/messages/PacketsMessage.java
  64. 26
      fine-socketio/src/com/fr/third/socketio/messages/XHROptionsMessage.java
  65. 26
      fine-socketio/src/com/fr/third/socketio/messages/XHRPostMessage.java
  66. 86
      fine-socketio/src/com/fr/third/socketio/misc/CompositeIterable.java
  67. 49
      fine-socketio/src/com/fr/third/socketio/misc/IterableCollection.java
  68. 39
      fine-socketio/src/com/fr/third/socketio/namespace/EventEntry.java
  69. 381
      fine-socketio/src/com/fr/third/socketio/namespace/Namespace.java
  70. 75
      fine-socketio/src/com/fr/third/socketio/namespace/NamespacesHub.java
  71. 33
      fine-socketio/src/com/fr/third/socketio/protocol/AckArgs.java
  72. 52
      fine-socketio/src/com/fr/third/socketio/protocol/AuthPacket.java
  73. 42
      fine-socketio/src/com/fr/third/socketio/protocol/Event.java
  74. 360
      fine-socketio/src/com/fr/third/socketio/protocol/JacksonJsonSupport.java
  75. 45
      fine-socketio/src/com/fr/third/socketio/protocol/JsonSupport.java
  76. 138
      fine-socketio/src/com/fr/third/socketio/protocol/Packet.java
  77. 313
      fine-socketio/src/com/fr/third/socketio/protocol/PacketDecoder.java
  78. 352
      fine-socketio/src/com/fr/third/socketio/protocol/PacketEncoder.java
  79. 60
      fine-socketio/src/com/fr/third/socketio/protocol/PacketType.java
  80. 126
      fine-socketio/src/com/fr/third/socketio/protocol/UTF8CharsScanner.java
  81. 36
      fine-socketio/src/com/fr/third/socketio/scheduler/CancelableScheduler.java
  82. 112
      fine-socketio/src/com/fr/third/socketio/scheduler/HashedWheelScheduler.java
  83. 133
      fine-socketio/src/com/fr/third/socketio/scheduler/HashedWheelTimeoutScheduler.java
  84. 60
      fine-socketio/src/com/fr/third/socketio/scheduler/SchedulerKey.java
  85. 41
      fine-socketio/src/com/fr/third/socketio/store/MemoryPubSubStore.java
  86. 46
      fine-socketio/src/com/fr/third/socketio/store/MemoryStore.java
  87. 53
      fine-socketio/src/com/fr/third/socketio/store/MemoryStoreFactory.java
  88. 90
      fine-socketio/src/com/fr/third/socketio/store/RedissonPubSubStore.java
  89. 51
      fine-socketio/src/com/fr/third/socketio/store/RedissonStore.java
  90. 76
      fine-socketio/src/com/fr/third/socketio/store/RedissonStoreFactory.java
  91. 29
      fine-socketio/src/com/fr/third/socketio/store/Store.java
  92. 44
      fine-socketio/src/com/fr/third/socketio/store/StoreFactory.java
  93. 97
      fine-socketio/src/com/fr/third/socketio/store/pubsub/BaseStoreFactory.java
  94. 38
      fine-socketio/src/com/fr/third/socketio/store/pubsub/ConnectMessage.java
  95. 38
      fine-socketio/src/com/fr/third/socketio/store/pubsub/DisconnectMessage.java
  96. 49
      fine-socketio/src/com/fr/third/socketio/store/pubsub/DispatchMessage.java
  97. 50
      fine-socketio/src/com/fr/third/socketio/store/pubsub/JoinLeaveMessage.java
  98. 23
      fine-socketio/src/com/fr/third/socketio/store/pubsub/PubSubListener.java
  99. 34
      fine-socketio/src/com/fr/third/socketio/store/pubsub/PubSubMessage.java
  100. 29
      fine-socketio/src/com/fr/third/socketio/store/pubsub/PubSubStore.java
  101. Some files were not shown because too many files have changed in this diff Show More

6
build.third_step0.gradle

@ -5,11 +5,11 @@ tasks.withType(JavaCompile){
options.encoding = 'UTF-8'
destinationDir = file('build/classes/main')
}
//jdk版本
//jdk版本
sourceCompatibility=1.5
def jarname="fine-third-10.0.jar"
def classesDir='build/classes/main'
//lib下的jar到classes文件夹
//lib下的jar到classes文件夹
jar{
baseName="fine-third-10.0"
}
@ -23,7 +23,7 @@ sourceSets{
}
//
//
FileTree files =fileTree(dir:'./',include:'build.*.gradle')
def buildDir=files[0].path.substring(0,files[0].path.lastIndexOf ('/'))
def branchName=buildDir.substring(buildDir.lastIndexOf ('/')+1)

12
build.third_step1.gradle

@ -4,11 +4,11 @@ tasks.withType(JavaCompile){
options.encoding = 'UTF-8'
destinationDir = file('build/classes/1')
}
//jdk版本
//jdk版本
sourceCompatibility=1.5
def jarname="fine-third-10.0.jar"
def classesDir='build/classes/1'
//lib下的jar到classes文件夹
//lib下的jar到classes文件夹
jar{
baseName="fine-third_1-10.0"
}
@ -23,7 +23,7 @@ task unJar{
}
//
//
sourceSets{
main{
java{
@ -41,12 +41,12 @@ repositories{
mavenCentral()
}
//
//
FileTree files =fileTree(dir:'./',include:'build.*.gradle')
def buildDir=files[0].path.substring(0,files[0].path.lastIndexOf ('/'))
def branchName=buildDir.substring(buildDir.lastIndexOf ('/')+1)
//
//
dependencies{
compile fileTree(dir:"${srcDir}/fine-poi/lib",include:'**/*.jar')
compile fileTree(dir:"${srcDir}/fine-quartz/lib",include:'**/*.jar')
@ -56,7 +56,7 @@ dependencies{
testCompile 'junit:junit:4.12'
}
//
//
def dataContent ={def dir ->
copySpec{
from ("${dir}"){

12
build.third_step2.gradle

@ -4,18 +4,18 @@ tasks.withType(JavaCompile){
options.encoding = 'UTF-8'
destinationDir = file('build/classes/2')
}
//jdk版本
//jdk版本
sourceCompatibility=1.5
def jarname="fine-third-10.0.jar"
def classesDir='build/classes/2'
//lib下的jar到classes文件夹
//lib下的jar到classes文件夹
jar{
baseName="fine-third_2-10.0"
}
def srcDir="."
//
//
sourceSets{
main{
java{
@ -33,12 +33,12 @@ repositories{
mavenCentral()
}
//
//
FileTree files =fileTree(dir:'./',include:'build.*.gradle')
def buildDir=files[0].path.substring(0,files[0].path.lastIndexOf ('/'))
def branchName=buildDir.substring(buildDir.lastIndexOf ('/')+1)
//
//
dependencies{
compile fileTree(dir:"${srcDir}/fine-spring/lib",include:'**/*.jar')
compile fileTree(dir:"${srcDir}/build/libs/",include:'**/*.jar')
@ -47,7 +47,7 @@ dependencies{
testCompile 'junit:junit:4.12'
}
//
//
def dataContent ={def dir ->
copySpec{
from ("${dir}"){

12
build.third_step3.gradle

@ -4,19 +4,19 @@ tasks.withType(JavaCompile){
options.encoding = 'UTF-8'
destinationDir = file('build/classes/3')
}
//jdk版本
//jdk版本
sourceCompatibility=1.5
def jarname="fine-third-10.0.jar"
def classesDir='build/classes/3'
def ftpreport='E:/ftp/share/report/'
//lib下的jar到classes文件夹
//lib下的jar到classes文件夹
jar{
baseName="fine-third_3-10.0"
}
def srcDir="."
//
//
sourceSets{
main{
java{
@ -36,12 +36,12 @@ repositories{
mavenCentral()
}
//
//
FileTree files =fileTree(dir:'./',include:'build.*.gradle')
def buildDir=files[0].path.substring(0,files[0].path.lastIndexOf ('/'))
def branchName=buildDir.substring(buildDir.lastIndexOf ('/')+1)
//
//
dependencies{
compile fileTree(dir:"${srcDir}/fine-jboss-logging/lib",include:'**/*.jar')
compile fileTree(dir:"${srcDir}/build/libs/",include:'**/*.jar')
@ -50,7 +50,7 @@ dependencies{
testCompile 'junit:junit:4.12'
}
//
//
def dataContent ={def dir ->
copySpec{
from ("${dir}"){

12
build.third_step4.gradle

@ -4,19 +4,19 @@ tasks.withType(JavaCompile){
options.encoding = 'UTF-8'
destinationDir = file('build/classes/4')
}
//jdk版本
//jdk版本
sourceCompatibility=1.5
def jarname="fine-third-10.0.jar"
def classesDir='build/classes/4'
def ftpreport='E:/ftp/share/report/'
//lib下的jar到classes文件夹
//lib下的jar到classes文件夹
jar{
baseName="fine-third_4-10.0"
}
def srcDir="."
//
//
sourceSets{
main{
java{
@ -35,12 +35,12 @@ repositories{
mavenCentral()
}
//
//
FileTree files =fileTree(dir:'./',include:'build.*.gradle')
def buildDir=files[0].path.substring(0,files[0].path.lastIndexOf ('/'))
def branchName=buildDir.substring(buildDir.lastIndexOf ('/')+1)
//
//
dependencies{
compile fileTree(dir:"${srcDir}/fine-hibernate/lib",include:'**/*.jar')
compile fileTree(dir:"${srcDir}/build/libs/",include:'**/*.jar')
@ -50,7 +50,7 @@ dependencies{
}
//
//
def dataContent ={def dir ->
copySpec{
from ("${dir}"){

12
build.third_step5.gradle

@ -4,19 +4,19 @@ tasks.withType(JavaCompile){
options.encoding = 'UTF-8'
destinationDir = file('build/classes/5')
}
//jdk版本
//jdk版本
sourceCompatibility=1.5
def jarname="fine-third-10.0.jar"
def classesDir='build/classes/5'
def ftpreport='E:/ftp/share/report/'
//lib下的jar到classes文件夹
//lib下的jar到classes文件夹
jar{
baseName="fine-third_5-10.0"
}
def srcDir="."
//
//
sourceSets{
main{
java{
@ -33,12 +33,12 @@ repositories{
mavenCentral()
}
//
//
FileTree files =fileTree(dir:'./',include:'build.*.gradle')
def buildDir=files[0].path.substring(0,files[0].path.lastIndexOf ('/'))
def branchName=buildDir.substring(buildDir.lastIndexOf ('/')+1)
//
//
dependencies{
compile fileTree(dir:"${srcDir}/fine-druid/lib",include:'ojdbc7-12.1.0.jar')
compile fileTree(dir:"${srcDir}/fine-druid/lib",include:'**/*.jar')
@ -48,7 +48,7 @@ dependencies{
testCompile 'junit:junit:4.12'
}
//
//
def dataContent ={def dir ->
copySpec{
from ("${dir}"){

17
build.third_step6.gradle

@ -4,19 +4,19 @@ tasks.withType(JavaCompile){
options.encoding = 'UTF-8'
destinationDir = file('build/classes/6')
}
//ָ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>jdk<EFBFBD>
//jdk版本
sourceCompatibility=1.5
def jarname="fine-third-10.0.jar"
def classesDir='build/classes/6'
def ftpreport='E:/ftp/share/report/'
//<EFBFBD><EFBFBD>ѹlib<EFBFBD>µ<EFBFBD>jar<EFBFBD><EFBFBD>classes<EFBFBD>ļ<EFBFBD><EFBFBD><EFBFBD>
//lib下的jar到classes文件夹
jar{
baseName="fine-third_6-10.0"
}
def srcDir="."
//<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Դ<EFBFBD><EFBFBD>·<EFBFBD><EFBFBD>
//
sourceSets{
main{
java{
@ -33,7 +33,7 @@ sourceSets{
"${srcDir}/fine-jedis/src",
"${srcDir}/fine-jedis/resources",
"${srcDir}/fine-cssparser/src",
"${srcDir}/fine-socketio/src"
]
}
}
@ -43,25 +43,26 @@ sourceSets.main.output.classesDir = file('build/classes/6')
repositories{
mavenCentral()
}
}
//<EFBFBD><EFBFBD>ȡʲô<EFBFBD><EFBFBD>֧<EFBFBD><EFBFBD>
//
FileTree files =fileTree(dir:'./',include:'build.*.gradle')
def buildDir=files[0].path.substring(0,files[0].path.lastIndexOf ('/'))
def branchName=buildDir.substring(buildDir.lastIndexOf ('/')+1)
//ָ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
//
dependencies{
compile fileTree(dir:"${srcDir}/fine-jackson/lib",include:'**/*.jar')
compile fileTree(dir:"${srcDir}/fine-ehcache/lib",include:'**/*.jar')
compile fileTree(dir:"${srcDir}/fine-cssparser/lib",include:'**/*.jar')
compile fileTree(dir:"${srcDir}/fine-socketio/lib",include:'**/*.jar')
compile fileTree(dir:"${srcDir}/build/libs/",include:'**/*.jar')
compile fileTree(dir:"../../finereport-lib-base/${branchName}",include:'**/*.jar')
compile fileTree(dir:"../../finereport-lib-other/${branchName}",include:'**/*.jar')
testCompile 'junit:junit:4.12'
}
//ָ<EFBFBD><EFBFBD><EFBFBD>޷<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>·<EFBFBD><EFBFBD>
//
def dataContent ={def dir ->
copySpec{
from ("${dir}"){

BIN
fine-socketio/lib/netty-all-4.1.22.Final.jar

Binary file not shown.

91
fine-socketio/src/com/fr/third/socketio/AckCallback.java

@ -0,0 +1,91 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio;
/**
* Base ack callback class.
*
* Notifies about acknowledgement received from client
* via {@link #onSuccess} callback method.
*
* By default it may wait acknowledgement from client
* while {@link SocketIOClient} is alive. Timeout can be
* defined {@link #timeout} as constructor argument.
*
* This object is NOT actual anymore if {@link #onSuccess} or
* {@link #onTimeout} was executed.
*
* @param <T> - any serializable type
*
* @see VoidAckCallback
* @see MultiTypeAckCallback
*
*/
public abstract class AckCallback<T> {
protected final Class<T> resultClass;
protected final int timeout;
/**
* Create AckCallback
*
* @param resultClass - result class
*/
public AckCallback(Class<T> resultClass) {
this(resultClass, -1);
}
/**
* Creates AckCallback with timeout
*
* @param resultClass - result class
* @param timeout - callback timeout in seconds
*/
public AckCallback(Class<T> resultClass, int timeout) {
this.resultClass = resultClass;
this.timeout = timeout;
}
public int getTimeout() {
return timeout;
}
/**
* Executes only once when acknowledgement received from client.
*
* @param result - object sended by client
*/
public abstract void onSuccess(T result);
/**
* Invoked only once then <code>timeout</code> defined
*
*/
public void onTimeout() {
}
/**
* Returns class of argument in {@link #onSuccess} method
*
* @return - result class
*/
public Class<T> getResultClass() {
return resultClass;
}
}

39
fine-socketio/src/com/fr/third/socketio/AckMode.java

@ -0,0 +1,39 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio;
public enum AckMode {
/**
* Send ack-response automatically on each ack-request
* <b>skip</b> exceptions during packet handling
*/
AUTO,
/**
* Send ack-response automatically on each ack-request
* only after <b>success</b> packet handling
*/
AUTO_SUCCESS_ONLY,
/**
* Turn off auto ack-response sending.
* Use AckRequest.sendAckData to send ack-response each time.
*
*/
MANUAL
}

90
fine-socketio/src/com/fr/third/socketio/AckRequest.java

@ -0,0 +1,90 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import com.fr.third.socketio.listener.DataListener;
import com.fr.third.socketio.protocol.Packet;
import com.fr.third.socketio.protocol.PacketType;
/**
* Ack request received from Socket.IO client.
* You can always check is it <code>true</code> through
* {@link #isAckRequested()} method.
*
* You can call {@link #sendAckData} methods only during
* {@link DataListener#onData} invocation. If {@link #sendAckData}
* not called it will be invoked with empty arguments right after
* {@link DataListener#onData} method execution by server.
*
* This object is NOT actual anymore if {@link #sendAckData} was
* executed or {@link DataListener#onData} invocation finished.
*
*/
public class AckRequest {
private final Packet originalPacket;
private final SocketIOClient client;
private final AtomicBoolean sended = new AtomicBoolean();
public AckRequest(Packet originalPacket, SocketIOClient client) {
this.originalPacket = originalPacket;
this.client = client;
}
/**
* Check whether ack request was made
*
* @return true if ack requested by client
*/
public boolean isAckRequested() {
return originalPacket.isAckRequested();
}
/**
* Send ack data to client.
* Can be invoked only once during {@link DataListener#onData}
* method invocation.
*
* @param objs - ack data objects
*/
public void sendAckData(Object ... objs) {
List<Object> args = Arrays.asList(objs);
sendAckData(args);
}
/**
* Send ack data to client.
* Can be invoked only once during {@link DataListener#onData}
* method invocation.
*
* @param objs - ack data object list
*/
public void sendAckData(List<Object> objs) {
if (!isAckRequested() || !sended.compareAndSet(false, true)) {
return;
}
Packet ackPacket = new Packet(PacketType.MESSAGE);
ackPacket.setSubType(PacketType.ACK);
ackPacket.setAckId(originalPacket.getAckId());
ackPacket.setData(objs);
client.send(ackPacket);
}
}

28
fine-socketio/src/com/fr/third/socketio/AuthorizationListener.java

@ -0,0 +1,28 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio;
public interface AuthorizationListener {
/**
* Checks is client with handshake data is authorized
*
* @param data - handshake data
* @return - <b>true</b> if client is authorized of <b>false</b> otherwise
*/
boolean isAuthorized(HandshakeData data);
}

82
fine-socketio/src/com/fr/third/socketio/BroadcastAckCallback.java

@ -0,0 +1,82 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
public class BroadcastAckCallback<T> {
final AtomicBoolean loopFinished = new AtomicBoolean();
final AtomicInteger counter = new AtomicInteger();
final AtomicBoolean successExecuted = new AtomicBoolean();
final Class<T> resultClass;
final int timeout;
public BroadcastAckCallback(Class<T> resultClass, int timeout) {
this.resultClass = resultClass;
this.timeout = timeout;
}
public BroadcastAckCallback(Class<T> resultClass) {
this(resultClass, -1);
}
final AckCallback<T> createClientCallback(final SocketIOClient client) {
counter.getAndIncrement();
return new AckCallback<T>(resultClass, timeout) {
@Override
public void onSuccess(T result) {
counter.getAndDecrement();
onClientSuccess(client, result);
executeSuccess();
}
@Override
public void onTimeout() {
onClientTimeout(client);
}
};
}
protected void onClientTimeout(SocketIOClient client) {
}
protected void onClientSuccess(SocketIOClient client, T result) {
}
protected void onAllSuccess() {
}
private void executeSuccess() {
if (loopFinished.get()
&& counter.get() == 0
&& successExecuted.compareAndSet(false, true)) {
onAllSuccess();
}
}
void loopFinished() {
loopFinished.set(true);
executeSuccess();
}
}

137
fine-socketio/src/com/fr/third/socketio/BroadcastOperations.java

@ -0,0 +1,137 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import com.fr.third.socketio.misc.IterableCollection;
import com.fr.third.socketio.namespace.Namespace;
import com.fr.third.socketio.protocol.Packet;
import com.fr.third.socketio.protocol.PacketType;
import com.fr.third.socketio.store.StoreFactory;
import com.fr.third.socketio.store.pubsub.DispatchMessage;
import com.fr.third.socketio.store.pubsub.PubSubType;
/**
* Fully thread-safe.
*
*/
public class BroadcastOperations implements ClientOperations {
private final Iterable<SocketIOClient> clients;
private final StoreFactory storeFactory;
public BroadcastOperations(Iterable<SocketIOClient> clients, StoreFactory storeFactory) {
super();
this.clients = clients;
this.storeFactory = storeFactory;
}
private void dispatch(Packet packet) {
Map<String, Set<String>> namespaceRooms = new HashMap<String, Set<String>>();
for (SocketIOClient socketIOClient : clients) {
Namespace namespace = (Namespace)socketIOClient.getNamespace();
Set<String> rooms = namespace.getRooms(socketIOClient);
Set<String> roomsList = namespaceRooms.get(namespace.getName());
if (roomsList == null) {
roomsList = new HashSet<String>();
namespaceRooms.put(namespace.getName(), roomsList);
}
roomsList.addAll(rooms);
}
for (Entry<String, Set<String>> entry : namespaceRooms.entrySet()) {
for (String room : entry.getValue()) {
storeFactory.pubSubStore().publish(PubSubType.DISPATCH, new DispatchMessage(room, packet, entry.getKey()));
}
}
}
public Collection<SocketIOClient> getClients() {
return new IterableCollection<SocketIOClient>(clients);
}
@Override
public void send(Packet packet) {
for (SocketIOClient client : clients) {
client.send(packet);
}
dispatch(packet);
}
public <T> void send(Packet packet, BroadcastAckCallback<T> ackCallback) {
for (SocketIOClient client : clients) {
client.send(packet, ackCallback.createClientCallback(client));
}
ackCallback.loopFinished();
}
@Override
public void disconnect() {
for (SocketIOClient client : clients) {
client.disconnect();
}
}
public void sendEvent(String name, SocketIOClient excludedClient, Object... data) {
Packet packet = new Packet(PacketType.MESSAGE);
packet.setSubType(PacketType.EVENT);
packet.setName(name);
packet.setData(Arrays.asList(data));
for (SocketIOClient client : clients) {
if (client.getSessionId().equals(excludedClient.getSessionId())) {
continue;
}
client.send(packet);
}
dispatch(packet);
}
@Override
public void sendEvent(String name, Object... data) {
Packet packet = new Packet(PacketType.MESSAGE);
packet.setSubType(PacketType.EVENT);
packet.setName(name);
packet.setData(Arrays.asList(data));
send(packet);
}
public <T> void sendEvent(String name, Object data, BroadcastAckCallback<T> ackCallback) {
for (SocketIOClient client : clients) {
client.sendEvent(name, ackCallback.createClientCallback(client), data);
}
ackCallback.loopFinished();
}
public <T> void sendEvent(String name, Object data, SocketIOClient excludedClient, BroadcastAckCallback<T> ackCallback) {
for (SocketIOClient client : clients) {
if (client.getSessionId().equals(excludedClient.getSessionId())) {
continue;
}
client.sendEvent(name, ackCallback.createClientCallback(client), data);
}
ackCallback.loopFinished();
}
}

49
fine-socketio/src/com/fr/third/socketio/ClientOperations.java

@ -0,0 +1,49 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio;
import com.fr.third.socketio.protocol.Packet;
/**
* Available client operations
*
*/
public interface ClientOperations {
/**
* Send custom packet.
* But {@link ClientOperations#sendEvent} method
* usage is enough for most cases.
*
* @param packet - packet to send
*/
void send(Packet packet);
/**
* Disconnect client
*
*/
void disconnect();
/**
* Send event
*
* @param name - event name
* @param data - event data
*/
void sendEvent(String name, Object ... data);
}

574
fine-socketio/src/com/fr/third/socketio/Configuration.java

@ -0,0 +1,574 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio;
import com.fr.third.socketio.handler.SuccessAuthorizationListener;
import com.fr.third.socketio.listener.DefaultExceptionListener;
import com.fr.third.socketio.listener.ExceptionListener;
import com.fr.third.socketio.protocol.JsonSupport;
import com.fr.third.socketio.store.MemoryStoreFactory;
import com.fr.third.socketio.store.StoreFactory;
import javax.net.ssl.KeyManagerFactory;
import java.io.InputStream;
import java.util.Arrays;
import java.util.List;
public class Configuration {
private ExceptionListener exceptionListener = new DefaultExceptionListener();
private String context = "/socket.io";
private List<Transport> transports = Arrays.asList(Transport.WEBSOCKET, Transport.POLLING);
private int bossThreads = 0; // 0 = current_processors_amount * 2
private int workerThreads = 0; // 0 = current_processors_amount * 2
private boolean useLinuxNativeEpoll;
private boolean allowCustomRequests = false;
private int upgradeTimeout = 10000;
private int pingTimeout = 60000;
private int pingInterval = 25000;
private int firstDataTimeout = 5000;
private int maxHttpContentLength = 64 * 1024;
private int maxFramePayloadLength = 64 * 1024;
private String packagePrefix;
private String hostname;
private int port = -1;
private String sslProtocol = "TLSv1";
private String keyStoreFormat = "JKS";
private InputStream keyStore;
private String keyStorePassword;
private String trustStoreFormat = "JKS";
private InputStream trustStore;
private String trustStorePassword;
private String keyManagerFactoryAlgorithm = KeyManagerFactory.getDefaultAlgorithm();
private boolean preferDirectBuffer = true;
private SocketConfig socketConfig = new SocketConfig();
private StoreFactory storeFactory = new MemoryStoreFactory();
private JsonSupport jsonSupport;
private AuthorizationListener authorizationListener = new SuccessAuthorizationListener();
private AckMode ackMode = AckMode.AUTO_SUCCESS_ONLY;
private boolean addVersionHeader = true;
private String origin;
private boolean httpCompression = true;
private boolean websocketCompression = true;
public Configuration() {
}
/**
* Defend from further modifications by cloning
*
* @param conf - Configuration object to clone
*/
Configuration(Configuration conf) {
setBossThreads(conf.getBossThreads());
setWorkerThreads(conf.getWorkerThreads());
setUseLinuxNativeEpoll(conf.isUseLinuxNativeEpoll());
setPingInterval(conf.getPingInterval());
setPingTimeout(conf.getPingTimeout());
setHostname(conf.getHostname());
setPort(conf.getPort());
if (conf.getJsonSupport() == null) {
try {
getClass().getClassLoader().loadClass("com.fasterxml.jackson.databind.ObjectMapper");
try {
Class<?> jjs = getClass().getClassLoader().loadClass("com.fr.third.socketio.protocol.JacksonJsonSupport");
JsonSupport js = (JsonSupport) jjs.getConstructor().newInstance();
conf.setJsonSupport(js);
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("Can't find jackson lib in classpath", e);
}
}
setJsonSupport(new JsonSupportWrapper(conf.getJsonSupport()));
setContext(conf.getContext());
setAllowCustomRequests(conf.isAllowCustomRequests());
setKeyStorePassword(conf.getKeyStorePassword());
setKeyStore(conf.getKeyStore());
setKeyStoreFormat(conf.getKeyStoreFormat());
setTrustStore(conf.getTrustStore());
setTrustStoreFormat(conf.getTrustStoreFormat());
setTrustStorePassword(conf.getTrustStorePassword());
setKeyManagerFactoryAlgorithm(conf.getKeyManagerFactoryAlgorithm());
setTransports(conf.getTransports().toArray(new Transport[conf.getTransports().size()]));
setMaxHttpContentLength(conf.getMaxHttpContentLength());
setPackagePrefix(conf.getPackagePrefix());
setPreferDirectBuffer(conf.isPreferDirectBuffer());
setStoreFactory(conf.getStoreFactory());
setAuthorizationListener(conf.getAuthorizationListener());
setExceptionListener(conf.getExceptionListener());
setSocketConfig(conf.getSocketConfig());
setAckMode(conf.getAckMode());
setMaxFramePayloadLength(conf.getMaxFramePayloadLength());
setUpgradeTimeout(conf.getUpgradeTimeout());
setAddVersionHeader(conf.isAddVersionHeader());
setOrigin(conf.getOrigin());
setSSLProtocol(conf.getSSLProtocol());
setHttpCompression(conf.isHttpCompression());
setWebsocketCompression(conf.isWebsocketCompression());
}
public JsonSupport getJsonSupport() {
return jsonSupport;
}
/**
* Allows to setup custom implementation of
* JSON serialization/deserialization
*
* @param jsonSupport - json mapper
*
* @see JsonSupport
*/
public void setJsonSupport(JsonSupport jsonSupport) {
this.jsonSupport = jsonSupport;
}
public String getHostname() {
return hostname;
}
/**
* Optional parameter. If not set then bind address
* will be 0.0.0.0 or ::0
*
* @param hostname - name of host
*/
public void setHostname(String hostname) {
this.hostname = hostname;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public int getBossThreads() {
return bossThreads;
}
public void setBossThreads(int bossThreads) {
this.bossThreads = bossThreads;
}
public int getWorkerThreads() {
return workerThreads;
}
public void setWorkerThreads(int workerThreads) {
this.workerThreads = workerThreads;
}
/**
* Ping interval
*
* @param heartbeatIntervalSecs - time in milliseconds
*/
public void setPingInterval(int heartbeatIntervalSecs) {
this.pingInterval = heartbeatIntervalSecs;
}
public int getPingInterval() {
return pingInterval;
}
/**
* Ping timeout
* Use <code>0</code> to disable it
*
* @param heartbeatTimeoutSecs - time in milliseconds
*/
public void setPingTimeout(int heartbeatTimeoutSecs) {
this.pingTimeout = heartbeatTimeoutSecs;
}
public int getPingTimeout() {
return pingTimeout;
}
public boolean isHeartbeatsEnabled() {
return pingTimeout > 0;
}
public String getContext() {
return context;
}
public void setContext(String context) {
this.context = context;
}
public boolean isAllowCustomRequests() {
return allowCustomRequests;
}
/**
* Allow to service custom requests differs from socket.io protocol.
* In this case it's necessary to add own handler which handle them
* to avoid hang connections.
* Default is {@code false}
*
* @param allowCustomRequests - {@code true} to allow
*/
public void setAllowCustomRequests(boolean allowCustomRequests) {
this.allowCustomRequests = allowCustomRequests;
}
/**
* SSL key store password
*
* @param keyStorePassword - password of key store
*/
public void setKeyStorePassword(String keyStorePassword) {
this.keyStorePassword = keyStorePassword;
}
public String getKeyStorePassword() {
return keyStorePassword;
}
/**
* SSL key store stream, maybe appointed to any source
*
* @param keyStore - key store input stream
*/
public void setKeyStore(InputStream keyStore) {
this.keyStore = keyStore;
}
public InputStream getKeyStore() {
return keyStore;
}
/**
* Key store format
*
* @param keyStoreFormat - key store format
*/
public void setKeyStoreFormat(String keyStoreFormat) {
this.keyStoreFormat = keyStoreFormat;
}
public String getKeyStoreFormat() {
return keyStoreFormat;
}
/**
* Set maximum http content length limit
*
* @param value
* the maximum length of the aggregated http content.
*/
public void setMaxHttpContentLength(int value) {
this.maxHttpContentLength = value;
}
public int getMaxHttpContentLength() {
return maxHttpContentLength;
}
/**
* Transports supported by server
*
* @param transports - list of transports
*/
public void setTransports(Transport ... transports) {
if (transports.length == 0) {
throw new IllegalArgumentException("Transports list can't be empty");
}
this.transports = Arrays.asList(transports);
}
public List<Transport> getTransports() {
return transports;
}
/**
* Package prefix for sending json-object from client
* without full class name.
*
* With defined package prefix socket.io client
* just need to define '@class: 'SomeType'' in json object
* instead of '@class: 'com.full.package.name.SomeType''
*
* @param packagePrefix - prefix string
*
*/
public void setPackagePrefix(String packagePrefix) {
this.packagePrefix = packagePrefix;
}
public String getPackagePrefix() {
return packagePrefix;
}
/**
* Buffer allocation method used during packet encoding.
* Default is {@code true}
*
* @param preferDirectBuffer {@code true} if a direct buffer should be tried to be used as target for
* the encoded messages. If {@code false} is used it will allocate a heap
* buffer, which is backed by an byte array.
*/
public void setPreferDirectBuffer(boolean preferDirectBuffer) {
this.preferDirectBuffer = preferDirectBuffer;
}
public boolean isPreferDirectBuffer() {
return preferDirectBuffer;
}
/**
* Data store - used to store session data and implements distributed pubsub.
* Default is {@code MemoryStoreFactory}
*
* @param clientStoreFactory - implements StoreFactory
*
* @see MemoryStoreFactory
*/
public void setStoreFactory(StoreFactory clientStoreFactory) {
this.storeFactory = clientStoreFactory;
}
public StoreFactory getStoreFactory() {
return storeFactory;
}
/**
* Authorization listener invoked on every handshake.
* Accepts or denies a client by {@code AuthorizationListener.isAuthorized} method.
* <b>Accepts</b> all clients by default.
*
* @param authorizationListener - authorization listener itself
*
* @see AuthorizationListener
*/
public void setAuthorizationListener(AuthorizationListener authorizationListener) {
this.authorizationListener = authorizationListener;
}
public AuthorizationListener getAuthorizationListener() {
return authorizationListener;
}
/**
* Exception listener invoked on any exception in
* SocketIO listener
*
* @param exceptionListener - listener
*
* @see ExceptionListener
*/
public void setExceptionListener(ExceptionListener exceptionListener) {
this.exceptionListener = exceptionListener;
}
public ExceptionListener getExceptionListener() {
return exceptionListener;
}
public SocketConfig getSocketConfig() {
return socketConfig;
}
/**
* TCP socket configuration
*
* @param socketConfig - config
*/
public void setSocketConfig(SocketConfig socketConfig) {
this.socketConfig = socketConfig;
}
/**
* Auto ack-response mode
* Default is {@code AckMode.AUTO_SUCCESS_ONLY}
*
* @see AckMode
*
* @param ackMode - ack mode
*/
public void setAckMode(AckMode ackMode) {
this.ackMode = ackMode;
}
public AckMode getAckMode() {
return ackMode;
}
public String getTrustStoreFormat() {
return trustStoreFormat;
}
public void setTrustStoreFormat(String trustStoreFormat) {
this.trustStoreFormat = trustStoreFormat;
}
public InputStream getTrustStore() {
return trustStore;
}
public void setTrustStore(InputStream trustStore) {
this.trustStore = trustStore;
}
public String getTrustStorePassword() {
return trustStorePassword;
}
public void setTrustStorePassword(String trustStorePassword) {
this.trustStorePassword = trustStorePassword;
}
public String getKeyManagerFactoryAlgorithm() {
return keyManagerFactoryAlgorithm;
}
public void setKeyManagerFactoryAlgorithm(String keyManagerFactoryAlgorithm) {
this.keyManagerFactoryAlgorithm = keyManagerFactoryAlgorithm;
}
/**
* Set maximum websocket frame content length limit
*
* @param maxFramePayloadLength - length
*/
public void setMaxFramePayloadLength(int maxFramePayloadLength) {
this.maxFramePayloadLength = maxFramePayloadLength;
}
public int getMaxFramePayloadLength() {
return maxFramePayloadLength;
}
/**
* Transport upgrade timeout in milliseconds
*
* @param upgradeTimeout - upgrade timeout
*/
public void setUpgradeTimeout(int upgradeTimeout) {
this.upgradeTimeout = upgradeTimeout;
}
public int getUpgradeTimeout() {
return upgradeTimeout;
}
/**
* Adds <b>Server</b> header with lib version to http response.
* <p>
* Default is <code>true</code>
*
* @param addVersionHeader - <code>true</code> to add header
*/
public void setAddVersionHeader(boolean addVersionHeader) {
this.addVersionHeader = addVersionHeader;
}
public boolean isAddVersionHeader() {
return addVersionHeader;
}
/**
* Set <b>Access-Control-Allow-Origin</b> header value for http each
* response.
* Default is <code>null</code>
*
* If value is <code>null</code> then request <b>ORIGIN</b> header value used.
*
* @param origin - origin
*/
public void setOrigin(String origin) {
this.origin = origin;
}
public String getOrigin() {
return origin;
}
public boolean isUseLinuxNativeEpoll() {
return useLinuxNativeEpoll;
}
public void setUseLinuxNativeEpoll(boolean useLinuxNativeEpoll) {
this.useLinuxNativeEpoll = useLinuxNativeEpoll;
}
/**
* Set the name of the requested SSL protocol
*
* @param sslProtocol - name of protocol
*/
public void setSSLProtocol(String sslProtocol) {
this.sslProtocol = sslProtocol;
}
public String getSSLProtocol() {
return sslProtocol;
}
/**
* Timeout between channel opening and first data transfer
* Helps to avoid 'silent channel' attack and prevents
* 'Too many open files' problem in this case
*
* @param firstDataTimeout - timeout value
*/
public void setFirstDataTimeout(int firstDataTimeout) {
this.firstDataTimeout = firstDataTimeout;
}
public int getFirstDataTimeout() {
return firstDataTimeout;
}
/**
* Activate http protocol compression. Uses {@code gzip} or
* {@code deflate} encoding choice depends on the {@code "Accept-Encoding"} header value.
* <p>
* Default is <code>true</code>
*
* @param httpCompression - <code>true</code> to use http compression
*/
public void setHttpCompression(boolean httpCompression) {
this.httpCompression = httpCompression;
}
public boolean isHttpCompression() {
return httpCompression;
}
/**
* Activate websocket protocol compression.
* Uses {@code permessage-deflate} encoding only.
* <p>
* Default is <code>true</code>
*
* @param websocketCompression - <code>true</code> to use websocket compression
*/
public void setWebsocketCompression(boolean websocketCompression) {
this.websocketCompression = websocketCompression;
}
public boolean isWebsocketCompression() {
return websocketCompression;
}
}

26
fine-socketio/src/com/fr/third/socketio/Disconnectable.java

@ -0,0 +1,26 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio;
import com.fr.third.socketio.handler.ClientHead;
public interface Disconnectable {
void onDisconnect(ClientHead client);
}

20
fine-socketio/src/com/fr/third/socketio/DisconnectableHub.java

@ -0,0 +1,20 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio;
public interface DisconnectableHub extends Disconnectable {
}

122
fine-socketio/src/com/fr/third/socketio/HandshakeData.java

@ -0,0 +1,122 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio;
import java.io.Serializable;
import java.net.InetSocketAddress;
import java.util.Date;
import java.util.List;
import java.util.Map;
import io.netty.handler.codec.http.HttpHeaders;
public class HandshakeData implements Serializable {
private static final long serialVersionUID = 1196350300161819978L;
private HttpHeaders headers;
private InetSocketAddress address;
private Date time = new Date();
private InetSocketAddress local;
private String url;
private Map<String, List<String>> urlParams;
private boolean xdomain;
// needed for correct deserialization
public HandshakeData() {
}
public HandshakeData(HttpHeaders headers, Map<String, List<String>> urlParams, InetSocketAddress address, String url, boolean xdomain) {
this(headers, urlParams, address, null, url, xdomain);
}
public HandshakeData(HttpHeaders headers, Map<String, List<String>> urlParams, InetSocketAddress address, InetSocketAddress local, String url, boolean xdomain) {
super();
this.headers = headers;
this.urlParams = urlParams;
this.address = address;
this.local = local;
this.url = url;
this.xdomain = xdomain;
}
/**
* Client network address
*
* @return network address
*/
public InetSocketAddress getAddress() {
return address;
}
/**
* Connection local address
*
* @return local address
*/
public InetSocketAddress getLocal() {
return local;
}
/**
* Http headers sent during first client request
*
* @return headers
*/
public HttpHeaders getHttpHeaders() {
return headers;
}
/**
* Client connection date
*
* @return date
*/
public Date getTime() {
return time;
}
/**
* Url used by client during first request
*
* @return url
*/
public String getUrl() {
return url;
}
public boolean isXdomain() {
return xdomain;
}
/**
* Url params stored in url used by client during first request
*
* @return map
*/
public Map<String, List<String>> getUrlParams() {
return urlParams;
}
public String getSingleUrlParam(String name) {
List<String> values = urlParams.get(name);
if (values != null && values.size() == 1) {
return values.iterator().next();
}
return null;
}
}

87
fine-socketio/src/com/fr/third/socketio/JsonSupportWrapper.java

@ -0,0 +1,87 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio;
import io.netty.buffer.ByteBufInputStream;
import io.netty.buffer.ByteBufOutputStream;
import java.io.IOException;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fr.third.socketio.protocol.AckArgs;
import com.fr.third.socketio.protocol.JsonSupport;
class JsonSupportWrapper implements JsonSupport {
private static final Logger log = LoggerFactory.getLogger(JsonSupportWrapper.class);
private final JsonSupport delegate;
JsonSupportWrapper(JsonSupport delegate) {
this.delegate = delegate;
}
@Override
public AckArgs readAckArgs(ByteBufInputStream src, AckCallback<?> callback) throws IOException {
try {
return delegate.readAckArgs(src, callback);
} catch (Exception e) {
src.reset();
log.error("Can't read ack args: " + src.readLine() + " for type: " + callback.getResultClass(), e);
throw new IOException(e);
}
}
@Override
public <T> T readValue(String namespaceName, ByteBufInputStream src, Class<T> valueType) throws IOException {
try {
return delegate.readValue(namespaceName, src, valueType);
} catch (Exception e) {
src.reset();
log.error("Can't read value: " + src.readLine() + " for type: " + valueType, e);
throw new IOException(e);
}
}
@Override
public void writeValue(ByteBufOutputStream out, Object value) throws IOException {
try {
delegate.writeValue(out, value);
} catch (Exception e) {
log.error("Can't write value: " + value, e);
throw new IOException(e);
}
}
@Override
public void addEventMapping(String namespaceName, String eventName, Class<?> ... eventClass) {
delegate.addEventMapping(namespaceName, eventName, eventClass);
}
@Override
public void removeEventMapping(String namespaceName, String eventName) {
delegate.removeEventMapping(namespaceName, eventName);
}
@Override
public List<byte[]> getArrays() {
return delegate.getArrays();
}
}

35
fine-socketio/src/com/fr/third/socketio/MultiTypeAckCallback.java

@ -0,0 +1,35 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio;
/**
* Multi type ack callback used in case of multiple ack arguments
*
*/
public abstract class MultiTypeAckCallback extends AckCallback<MultiTypeArgs> {
private Class<?>[] resultClasses;
public MultiTypeAckCallback(Class<?> ... resultClasses) {
super(MultiTypeArgs.class);
this.resultClasses = resultClasses;
}
public Class<?>[] getResultClasses() {
return resultClasses;
}
}

69
fine-socketio/src/com/fr/third/socketio/MultiTypeArgs.java

@ -0,0 +1,69 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio;
import java.util.Iterator;
import java.util.List;
public class MultiTypeArgs implements Iterable<Object> {
private final List<Object> args;
public MultiTypeArgs(List<Object> args) {
super();
this.args = args;
}
public boolean isEmpty() {
return size() == 0;
}
public int size() {
return args.size();
}
public List<Object> getArgs() {
return args;
}
public <T> T first() {
return get(0);
}
public <T> T second() {
return get(1);
}
/**
* "index out of bounds"-safe method for getting elements
*
* @param <T> type of argument
* @param index to get
* @return argument
*/
public <T> T get(int index) {
if (size() <= index) {
return null;
}
return (T) args.get(index);
}
@Override
public Iterator<Object> iterator() {
return args.iterator();
}
}

89
fine-socketio/src/com/fr/third/socketio/SocketConfig.java

@ -0,0 +1,89 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio;
/**
* TCP socket configuration contains configuration for main server channel
* and client channels
*
* @see java.net.SocketOptions
*/
public class SocketConfig {
private boolean tcpNoDelay = true;
private int tcpSendBufferSize = -1;
private int tcpReceiveBufferSize = -1;
private boolean tcpKeepAlive = false;
private int soLinger = -1;
private boolean reuseAddress = false;
private int acceptBackLog = 1024;
public boolean isTcpNoDelay() {
return tcpNoDelay;
}
public void setTcpNoDelay(boolean tcpNoDelay) {
this.tcpNoDelay = tcpNoDelay;
}
public int getTcpSendBufferSize() {
return tcpSendBufferSize;
}
public void setTcpSendBufferSize(int tcpSendBufferSize) {
this.tcpSendBufferSize = tcpSendBufferSize;
}
public int getTcpReceiveBufferSize() {
return tcpReceiveBufferSize;
}
public void setTcpReceiveBufferSize(int tcpReceiveBufferSize) {
this.tcpReceiveBufferSize = tcpReceiveBufferSize;
}
public boolean isTcpKeepAlive() {
return tcpKeepAlive;
}
public void setTcpKeepAlive(boolean tcpKeepAlive) {
this.tcpKeepAlive = tcpKeepAlive;
}
public int getSoLinger() {
return soLinger;
}
public void setSoLinger(int soLinger) {
this.soLinger = soLinger;
}
public boolean isReuseAddress() {
return reuseAddress;
}
public void setReuseAddress(boolean reuseAddress) {
this.reuseAddress = reuseAddress;
}
public int getAcceptBackLog() {
return acceptBackLog;
}
public void setAcceptBackLog(int acceptBackLog) {
this.acceptBackLog = acceptBackLog;
}
}

236
fine-socketio/src/com/fr/third/socketio/SocketIOChannelInitializer.java

@ -0,0 +1,236 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio;
import java.security.KeyStore;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fr.third.socketio.ack.AckManager;
import com.fr.third.socketio.handler.AuthorizeHandler;
import com.fr.third.socketio.handler.ClientHead;
import com.fr.third.socketio.handler.ClientsBox;
import com.fr.third.socketio.handler.EncoderHandler;
import com.fr.third.socketio.handler.InPacketHandler;
import com.fr.third.socketio.handler.PacketListener;
import com.fr.third.socketio.handler.WrongUrlHandler;
import com.fr.third.socketio.namespace.NamespacesHub;
import com.fr.third.socketio.protocol.JsonSupport;
import com.fr.third.socketio.protocol.PacketDecoder;
import com.fr.third.socketio.protocol.PacketEncoder;
import com.fr.third.socketio.scheduler.CancelableScheduler;
import com.fr.third.socketio.scheduler.HashedWheelTimeoutScheduler;
import com.fr.third.socketio.store.StoreFactory;
import com.fr.third.socketio.store.pubsub.DisconnectMessage;
import com.fr.third.socketio.store.pubsub.PubSubType;
import com.fr.third.socketio.transport.PollingTransport;
import com.fr.third.socketio.transport.WebSocketTransport;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.http.HttpContentCompressor;
import io.netty.handler.codec.http.HttpMessage;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketServerCompressionHandler;
import io.netty.handler.ssl.SslHandler;
public class SocketIOChannelInitializer extends ChannelInitializer<Channel> implements DisconnectableHub {
public static final String SOCKETIO_ENCODER = "socketioEncoder";
public static final String WEB_SOCKET_TRANSPORT_COMPRESSION = "webSocketTransportCompression";
public static final String WEB_SOCKET_TRANSPORT = "webSocketTransport";
public static final String WEB_SOCKET_AGGREGATOR = "webSocketAggregator";
public static final String XHR_POLLING_TRANSPORT = "xhrPollingTransport";
public static final String AUTHORIZE_HANDLER = "authorizeHandler";
public static final String PACKET_HANDLER = "packetHandler";
public static final String HTTP_ENCODER = "httpEncoder";
public static final String HTTP_COMPRESSION = "httpCompression";
public static final String HTTP_AGGREGATOR = "httpAggregator";
public static final String HTTP_REQUEST_DECODER = "httpDecoder";
public static final String SSL_HANDLER = "ssl";
public static final String RESOURCE_HANDLER = "resourceHandler";
public static final String WRONG_URL_HANDLER = "wrongUrlBlocker";
private static final Logger log = LoggerFactory.getLogger(SocketIOChannelInitializer.class);
private AckManager ackManager;
private ClientsBox clientsBox = new ClientsBox();
private AuthorizeHandler authorizeHandler;
private PollingTransport xhrPollingTransport;
private WebSocketTransport webSocketTransport;
private WebSocketServerCompressionHandler webSocketTransportCompression = new WebSocketServerCompressionHandler();
private EncoderHandler encoderHandler;
private WrongUrlHandler wrongUrlHandler;
private CancelableScheduler scheduler = new HashedWheelTimeoutScheduler();
private InPacketHandler packetHandler;
private SSLContext sslContext;
private Configuration configuration;
@Override
public void handlerAdded(ChannelHandlerContext ctx) {
scheduler.update(ctx);
}
public void start(Configuration configuration, NamespacesHub namespacesHub) {
this.configuration = configuration;
ackManager = new AckManager(scheduler);
JsonSupport jsonSupport = configuration.getJsonSupport();
PacketEncoder encoder = new PacketEncoder(configuration, jsonSupport);
PacketDecoder decoder = new PacketDecoder(jsonSupport, ackManager);
String connectPath = configuration.getContext() + "/";
boolean isSsl = configuration.getKeyStore() != null;
if (isSsl) {
try {
sslContext = createSSLContext(configuration);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
StoreFactory factory = configuration.getStoreFactory();
authorizeHandler = new AuthorizeHandler(connectPath, scheduler, configuration, namespacesHub, factory, this, ackManager, clientsBox);
factory.init(namespacesHub, authorizeHandler, jsonSupport);
xhrPollingTransport = new PollingTransport(decoder, authorizeHandler, clientsBox);
webSocketTransport = new WebSocketTransport(isSsl, authorizeHandler, configuration, scheduler, clientsBox);
PacketListener packetListener = new PacketListener(ackManager, namespacesHub, xhrPollingTransport, scheduler);
packetHandler = new InPacketHandler(packetListener, decoder, namespacesHub, configuration.getExceptionListener());
try {
encoderHandler = new EncoderHandler(configuration, encoder);
} catch (Exception e) {
throw new IllegalStateException(e);
}
wrongUrlHandler = new WrongUrlHandler();
}
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
addSslHandler(pipeline);
addSocketioHandlers(pipeline);
}
/**
* Adds the ssl handler
*
* @param pipeline - channel pipeline
*/
protected void addSslHandler(ChannelPipeline pipeline) {
if (sslContext != null) {
SSLEngine engine = sslContext.createSSLEngine();
engine.setUseClientMode(false);
pipeline.addLast(SSL_HANDLER, new SslHandler(engine));
}
}
/**
* Adds the socketio channel handlers
*
* @param pipeline - channel pipeline
*/
protected void addSocketioHandlers(ChannelPipeline pipeline) {
pipeline.addLast(HTTP_REQUEST_DECODER, new HttpRequestDecoder());
pipeline.addLast(HTTP_AGGREGATOR, new HttpObjectAggregator(configuration.getMaxHttpContentLength()) {
@Override
protected Object newContinueResponse(HttpMessage start, int maxContentLength,
ChannelPipeline pipeline) {
return null;
}
});
pipeline.addLast(HTTP_ENCODER, new HttpResponseEncoder());
if (configuration.isHttpCompression()) {
pipeline.addLast(HTTP_COMPRESSION, new HttpContentCompressor());
}
pipeline.addLast(PACKET_HANDLER, packetHandler);
pipeline.addLast(AUTHORIZE_HANDLER, authorizeHandler);
pipeline.addLast(XHR_POLLING_TRANSPORT, xhrPollingTransport);
// TODO use single instance when https://github.com/netty/netty/issues/4755 will be resolved
if (configuration.isWebsocketCompression()) {
pipeline.addLast(WEB_SOCKET_TRANSPORT_COMPRESSION, new WebSocketServerCompressionHandler());
}
pipeline.addLast(WEB_SOCKET_TRANSPORT, webSocketTransport);
pipeline.addLast(SOCKETIO_ENCODER, encoderHandler);
pipeline.addLast(WRONG_URL_HANDLER, wrongUrlHandler);
}
private SSLContext createSSLContext(Configuration configuration) throws Exception {
TrustManager[] managers = null;
if (configuration.getTrustStore() != null) {
KeyStore ts = KeyStore.getInstance(configuration.getTrustStoreFormat());
ts.load(configuration.getTrustStore(), configuration.getTrustStorePassword().toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ts);
managers = tmf.getTrustManagers();
}
KeyStore ks = KeyStore.getInstance(configuration.getKeyStoreFormat());
ks.load(configuration.getKeyStore(), configuration.getKeyStorePassword().toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance(configuration.getKeyManagerFactoryAlgorithm());
kmf.init(ks, configuration.getKeyStorePassword().toCharArray());
SSLContext serverContext = SSLContext.getInstance(configuration.getSSLProtocol());
serverContext.init(kmf.getKeyManagers(), managers, null);
return serverContext;
}
@Override
public void onDisconnect(ClientHead client) {
ackManager.onDisconnect(client);
authorizeHandler.onDisconnect(client);
configuration.getStoreFactory().onDisconnect(client);
configuration.getStoreFactory().pubSubStore().publish(PubSubType.DISCONNECT, new DisconnectMessage(client.getSessionId()));
log.debug("Client with sessionId: {} disconnected", client.getSessionId());
}
public void stop() {
StoreFactory factory = configuration.getStoreFactory();
factory.shutdown();
scheduler.shutdown();
}
}

112
fine-socketio/src/com/fr/third/socketio/SocketIOClient.java

@ -0,0 +1,112 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio;
import java.net.SocketAddress;
import java.util.Set;
import java.util.UUID;
import com.fr.third.socketio.protocol.Packet;
import com.fr.third.socketio.store.Store;
/**
* Fully thread-safe.
*
*/
public interface SocketIOClient extends ClientOperations, Store {
/**
* Handshake data used during client connection
*
* @return HandshakeData
*/
HandshakeData getHandshakeData();
/**
* Current client transport protocol
*
* @return transport protocol
*/
Transport getTransport();
/**
* Send event with ack callback
*
* @param name - event name
* @param data - event data
* @param ackCallback - ack callback
*/
void sendEvent(String name, AckCallback<?> ackCallback, Object ... data);
/**
* Send packet with ack callback
*
* @param packet - packet to send
* @param ackCallback - ack callback
*/
void send(Packet packet, AckCallback<?> ackCallback);
/**
* Client namespace
*
* @return - namespace
*/
SocketIONamespace getNamespace();
/**
* Client session id, uses {@link UUID} object
*
* @return - session id
*/
UUID getSessionId();
/**
* Get client remote address
*
* @return remote address
*/
SocketAddress getRemoteAddress();
/**
* Check is underlying channel open
*
* @return <code>true</code> if channel open, otherwise <code>false</code>
*/
boolean isChannelOpen();
/**
* Join client to room
*
* @param room - name of room
*/
void joinRoom(String room);
/**
* Join client to room
*
* @param room - name of room
*/
void leaveRoom(String room);
/**
* Get all rooms a client is joined in.
*
* @return name of rooms
*/
Set<String> getAllRooms();
}

50
fine-socketio/src/com/fr/third/socketio/SocketIONamespace.java

@ -0,0 +1,50 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio;
import java.util.Collection;
import java.util.UUID;
import com.fr.third.socketio.listener.ClientListeners;
/**
* Fully thread-safe.
*
*/
public interface SocketIONamespace extends ClientListeners {
String getName();
BroadcastOperations getBroadcastOperations();
BroadcastOperations getRoomOperations(String room);
/**
* Get all clients connected to namespace
*
* @return collection of clients
*/
Collection<SocketIOClient> getAllClients();
/**
* Get client by uuid connected to namespace
*
* @param uuid - id of client
* @return client
*/
SocketIOClient getClient(UUID uuid);
}

269
fine-socketio/src/com/fr/third/socketio/SocketIOServer.java

@ -0,0 +1,269 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio;
import com.fr.third.socketio.listener.*;
import com.fr.third.socketio.listener.ClientListeners;
import com.fr.third.socketio.listener.ConnectListener;
import com.fr.third.socketio.listener.DataListener;
import com.fr.third.socketio.listener.DisconnectListener;
import com.fr.third.socketio.listener.MultiTypeEventListener;
import com.fr.third.socketio.listener.PingListener;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.FixedRecvByteBufAllocator;
import io.netty.channel.ServerChannel;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollServerSocketChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
import java.net.InetSocketAddress;
import java.util.Collection;
import java.util.UUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fr.third.socketio.namespace.Namespace;
import com.fr.third.socketio.namespace.NamespacesHub;
/**
* Fully thread-safe.
*
*/
public class SocketIOServer implements ClientListeners {
private static final Logger log = LoggerFactory.getLogger(SocketIOServer.class);
private final Configuration configCopy;
private final Configuration configuration;
private final NamespacesHub namespacesHub;
private final SocketIONamespace mainNamespace;
private SocketIOChannelInitializer pipelineFactory = new SocketIOChannelInitializer();
private EventLoopGroup bossGroup;
private EventLoopGroup workerGroup;
public SocketIOServer(Configuration configuration) {
this.configuration = configuration;
this.configCopy = new Configuration(configuration);
namespacesHub = new NamespacesHub(configCopy);
mainNamespace = addNamespace(Namespace.DEFAULT_NAME);
}
public void setPipelineFactory(SocketIOChannelInitializer pipelineFactory) {
this.pipelineFactory = pipelineFactory;
}
/**
* Get all clients connected to default namespace
*
* @return clients collection
*/
public Collection<SocketIOClient> getAllClients() {
return namespacesHub.get(Namespace.DEFAULT_NAME).getAllClients();
}
/**
* Get client by uuid from default namespace
*
* @param uuid - id of client
* @return client
*/
public SocketIOClient getClient(UUID uuid) {
return namespacesHub.get(Namespace.DEFAULT_NAME).getClient(uuid);
}
/**
* Get all namespaces
*
* @return namespaces collection
*/
public Collection<SocketIONamespace> getAllNamespaces() {
return namespacesHub.getAllNamespaces();
}
public BroadcastOperations getBroadcastOperations() {
return new BroadcastOperations(getAllClients(), configCopy.getStoreFactory());
}
/**
* Get broadcast operations for clients within
* room by <code>room</code> name
*
* @param room - name of room
* @return broadcast operations
*/
public BroadcastOperations getRoomOperations(String room) {
Iterable<SocketIOClient> clients = namespacesHub.getRoomClients(room);
return new BroadcastOperations(clients, configCopy.getStoreFactory());
}
/**
* Start server
*/
public void start() {
startAsync().syncUninterruptibly();
}
/**
* Start server asynchronously
*
* @return void
*/
public Future<Void> startAsync() {
log.info("Session store / pubsub factory used: {}", configCopy.getStoreFactory());
initGroups();
pipelineFactory.start(configCopy, namespacesHub);
Class<? extends ServerChannel> channelClass = NioServerSocketChannel.class;
if (configCopy.isUseLinuxNativeEpoll()) {
channelClass = EpollServerSocketChannel.class;
}
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(channelClass)
.childHandler(pipelineFactory);
applyConnectionOptions(b);
InetSocketAddress addr = new InetSocketAddress(configCopy.getPort());
if (configCopy.getHostname() != null) {
addr = new InetSocketAddress(configCopy.getHostname(), configCopy.getPort());
}
return b.bind(addr).addListener(new FutureListener<Void>() {
@Override
public void operationComplete(Future<Void> future) throws Exception {
if (future.isSuccess()) {
log.info("SocketIO server started at port: {}", configCopy.getPort());
} else {
log.error("SocketIO server start failed at port: {}!", configCopy.getPort());
}
}
});
}
protected void applyConnectionOptions(ServerBootstrap bootstrap) {
SocketConfig config = configCopy.getSocketConfig();
bootstrap.childOption(ChannelOption.TCP_NODELAY, config.isTcpNoDelay());
if (config.getTcpSendBufferSize() != -1) {
bootstrap.childOption(ChannelOption.SO_SNDBUF, config.getTcpSendBufferSize());
}
if (config.getTcpReceiveBufferSize() != -1) {
bootstrap.childOption(ChannelOption.SO_RCVBUF, config.getTcpReceiveBufferSize());
bootstrap.childOption(ChannelOption.RCVBUF_ALLOCATOR, new FixedRecvByteBufAllocator(config.getTcpReceiveBufferSize()));
}
bootstrap.childOption(ChannelOption.SO_KEEPALIVE, config.isTcpKeepAlive());
bootstrap.childOption(ChannelOption.SO_LINGER, config.getSoLinger());
bootstrap.option(ChannelOption.SO_REUSEADDR, config.isReuseAddress());
bootstrap.option(ChannelOption.SO_BACKLOG, config.getAcceptBackLog());
}
protected void initGroups() {
if (configCopy.isUseLinuxNativeEpoll()) {
bossGroup = new EpollEventLoopGroup(configCopy.getBossThreads());
workerGroup = new EpollEventLoopGroup(configCopy.getWorkerThreads());
} else {
bossGroup = new NioEventLoopGroup(configCopy.getBossThreads());
workerGroup = new NioEventLoopGroup(configCopy.getWorkerThreads());
}
}
/**
* Stop server
*/
public void stop() {
bossGroup.shutdownGracefully().syncUninterruptibly();
workerGroup.shutdownGracefully().syncUninterruptibly();
pipelineFactory.stop();
log.info("SocketIO server stopped");
}
public SocketIONamespace addNamespace(String name) {
return namespacesHub.create(name);
}
public SocketIONamespace getNamespace(String name) {
return namespacesHub.get(name);
}
public void removeNamespace(String name) {
namespacesHub.remove(name);
}
/**
* Allows to get configuration provided
* during server creation. Further changes on
* this object not affect server.
*
* @return Configuration object
*/
public Configuration getConfiguration() {
return configuration;
}
@Override
public void addMultiTypeEventListener(String eventName, MultiTypeEventListener listener, Class<?>... eventClass) {
mainNamespace.addMultiTypeEventListener(eventName, listener, eventClass);
}
@Override
public <T> void addEventListener(String eventName, Class<T> eventClass, DataListener<T> listener) {
mainNamespace.addEventListener(eventName, eventClass, listener);
}
@Override
public void removeAllListeners(String eventName) {
mainNamespace.removeAllListeners(eventName);
}
@Override
public void addDisconnectListener(DisconnectListener listener) {
mainNamespace.addDisconnectListener(listener);
}
@Override
public void addConnectListener(ConnectListener listener) {
mainNamespace.addConnectListener(listener);
}
@Override
public void addPingListener(PingListener listener) {
mainNamespace.addPingListener(listener);
}
@Override
public void addListeners(Object listeners) {
mainNamespace.addListeners(listeners);
}
@Override
public void addListeners(Object listeners, Class<?> listenersClass) {
mainNamespace.addListeners(listeners, listenersClass);
}
}

45
fine-socketio/src/com/fr/third/socketio/Transport.java

@ -0,0 +1,45 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio;
import com.fr.third.socketio.transport.WebSocketTransport;
import com.fr.third.socketio.transport.PollingTransport;
public enum Transport {
WEBSOCKET(WebSocketTransport.NAME),
POLLING(PollingTransport.NAME);
private final String value;
Transport(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public static Transport byName(String value) {
for (Transport t : Transport.values()) {
if (t.getValue().equals(value)) {
return t;
}
}
throw new IllegalArgumentException("Can't find " + value + " transport");
}
}

39
fine-socketio/src/com/fr/third/socketio/VoidAckCallback.java

@ -0,0 +1,39 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio;
/**
* Base ack callback with {@link Void} class as type.
*
*/
public abstract class VoidAckCallback extends AckCallback<Void> {
public VoidAckCallback() {
super(Void.class);
}
public VoidAckCallback(int timeout) {
super(Void.class, timeout);
}
@Override
public final void onSuccess(Void result) {
onSuccess();
}
protected abstract void onSuccess();
}

186
fine-socketio/src/com/fr/third/socketio/ack/AckManager.java

@ -0,0 +1,186 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.ack;
import com.fr.third.socketio.AckCallback;
import com.fr.third.socketio.Disconnectable;
import com.fr.third.socketio.MultiTypeAckCallback;
import com.fr.third.socketio.MultiTypeArgs;
import com.fr.third.socketio.SocketIOClient;
import com.fr.third.socketio.handler.ClientHead;
import com.fr.third.socketio.protocol.Packet;
import com.fr.third.socketio.scheduler.CancelableScheduler;
import com.fr.third.socketio.scheduler.SchedulerKey;
import io.netty.util.internal.PlatformDependent;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AckManager implements Disconnectable {
class AckEntry {
final Map<Long, AckCallback<?>> ackCallbacks = PlatformDependent.newConcurrentHashMap();
final AtomicLong ackIndex = new AtomicLong(-1);
public long addAckCallback(AckCallback<?> callback) {
long index = ackIndex.incrementAndGet();
ackCallbacks.put(index, callback);
return index;
}
public Set<Long> getAckIndexes() {
return ackCallbacks.keySet();
}
public AckCallback<?> getAckCallback(long index) {
return ackCallbacks.get(index);
}
public AckCallback<?> removeCallback(long index) {
return ackCallbacks.remove(index);
}
public void initAckIndex(long index) {
ackIndex.compareAndSet(-1, index);
}
}
private static final Logger log = LoggerFactory.getLogger(AckManager.class);
private final Map<UUID, AckEntry> ackEntries = PlatformDependent.newConcurrentHashMap();
private final CancelableScheduler scheduler;
public AckManager(CancelableScheduler scheduler) {
super();
this.scheduler = scheduler;
}
public void initAckIndex(UUID sessionId, long index) {
AckEntry ackEntry = getAckEntry(sessionId);
ackEntry.initAckIndex(index);
}
private AckEntry getAckEntry(UUID sessionId) {
AckEntry ackEntry = ackEntries.get(sessionId);
if (ackEntry == null) {
ackEntry = new AckEntry();
AckEntry oldAckEntry = ackEntries.put(sessionId, ackEntry);
if (oldAckEntry != null) {
ackEntry = oldAckEntry;
}
}
return ackEntry;
}
@SuppressWarnings("unchecked")
public void onAck(SocketIOClient client, Packet packet) {
AckSchedulerKey key = new AckSchedulerKey(SchedulerKey.Type.ACK_TIMEOUT, client.getSessionId(), packet.getAckId());
scheduler.cancel(key);
AckCallback callback = removeCallback(client.getSessionId(), packet.getAckId());
if (callback == null) {
return;
}
if (callback instanceof MultiTypeAckCallback) {
callback.onSuccess(new MultiTypeArgs(packet.<List<Object>>getData()));
} else {
Object param = null;
List<Object> args = packet.getData();
if (!args.isEmpty()) {
param = args.get(0);
}
if (args.size() > 1) {
log.error("Wrong ack args amount. Should be only one argument, but current amount is: {}. Ack id: {}, sessionId: {}",
args.size(), packet.getAckId(), client.getSessionId());
}
callback.onSuccess(param);
}
}
private AckCallback<?> removeCallback(UUID sessionId, long index) {
AckEntry ackEntry = ackEntries.get(sessionId);
// may be null if client disconnected
// before timeout occurs
if (ackEntry != null) {
return ackEntry.removeCallback(index);
}
return null;
}
public AckCallback<?> getCallback(UUID sessionId, long index) {
AckEntry ackEntry = getAckEntry(sessionId);
return ackEntry.getAckCallback(index);
}
public long registerAck(UUID sessionId, AckCallback<?> callback) {
AckEntry ackEntry = getAckEntry(sessionId);
ackEntry.initAckIndex(0);
long index = ackEntry.addAckCallback(callback);
if (log.isDebugEnabled()) {
log.debug("AckCallback registered with id: {} for client: {}", index, sessionId);
}
scheduleTimeout(index, sessionId, callback);
return index;
}
private void scheduleTimeout(final long index, final UUID sessionId, AckCallback<?> callback) {
if (callback.getTimeout() == -1) {
return;
}
SchedulerKey key = new AckSchedulerKey(SchedulerKey.Type.ACK_TIMEOUT, sessionId, index);
scheduler.scheduleCallback(key, new Runnable() {
@Override
public void run() {
AckCallback<?> cb = removeCallback(sessionId, index);
if (cb != null) {
cb.onTimeout();
}
}
}, callback.getTimeout(), TimeUnit.SECONDS);
}
@Override
public void onDisconnect(ClientHead client) {
AckEntry e = ackEntries.remove(client.getSessionId());
if (e == null) {
return;
}
Set<Long> indexes = e.getAckIndexes();
for (Long index : indexes) {
AckCallback<?> callback = e.getAckCallback(index);
if (callback != null) {
callback.onTimeout();
}
SchedulerKey key = new AckSchedulerKey(SchedulerKey.Type.ACK_TIMEOUT, client.getSessionId(), index);
scheduler.cancel(key);
}
}
}

57
fine-socketio/src/com/fr/third/socketio/ack/AckSchedulerKey.java

@ -0,0 +1,57 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.ack;
import java.util.UUID;
import com.fr.third.socketio.scheduler.SchedulerKey;
public class AckSchedulerKey extends SchedulerKey {
private final long index;
public AckSchedulerKey(Type type, UUID sessionId, long index) {
super(type, sessionId);
this.index = index;
}
public long getIndex() {
return index;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + (int) (index ^ (index >>> 32));
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!super.equals(obj))
return false;
if (getClass() != obj.getClass())
return false;
AckSchedulerKey other = (AckSchedulerKey) obj;
if (index != other.index)
return false;
return true;
}
}

31
fine-socketio/src/com/fr/third/socketio/annotation/AnnotationScanner.java

@ -0,0 +1,31 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import com.fr.third.socketio.namespace.Namespace;
public interface AnnotationScanner {
Class<? extends Annotation> getScanAnnotation();
void addListener(Namespace namespace, Object object, Method method, Annotation annotation);
void validate(Method method, Class<?> clazz);
}

36
fine-socketio/src/com/fr/third/socketio/annotation/OnConnect.java

@ -0,0 +1,36 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation that defines <b>Connect</b> handler.
*
* Arguments in method:
*
* - SocketIOClient (required)
*
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OnConnect {
}

66
fine-socketio/src/com/fr/third/socketio/annotation/OnConnectScanner.java

@ -0,0 +1,66 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import com.fr.third.socketio.SocketIOClient;
import com.fr.third.socketio.handler.SocketIOException;
import com.fr.third.socketio.listener.ConnectListener;
import com.fr.third.socketio.namespace.Namespace;
public class OnConnectScanner implements AnnotationScanner {
@Override
public Class<? extends Annotation> getScanAnnotation() {
return OnConnect.class;
}
@Override
public void addListener(Namespace namespace, final Object object, final Method method, Annotation annotation) {
namespace.addConnectListener(new ConnectListener() {
@Override
public void onConnect(SocketIOClient client) {
try {
method.invoke(object, client);
} catch (InvocationTargetException e) {
throw new SocketIOException(e.getCause());
} catch (Exception e) {
throw new SocketIOException(e);
}
}
});
}
@Override
public void validate(Method method, Class<?> clazz) {
if (method.getParameterTypes().length != 1) {
throw new IllegalArgumentException("Wrong OnConnect listener signature: " + clazz + "." + method.getName());
}
boolean valid = false;
for (Class<?> eventType : method.getParameterTypes()) {
if (eventType.equals(SocketIOClient.class)) {
valid = true;
}
}
if (!valid) {
throw new IllegalArgumentException("Wrong OnConnect listener signature: " + clazz + "." + method.getName());
}
}
}

35
fine-socketio/src/com/fr/third/socketio/annotation/OnDisconnect.java

@ -0,0 +1,35 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation that defines <b>Disconnect</b> handler.
*
* Arguments in method:
*
* - SocketIOClient (required)
*
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OnDisconnect {
}

66
fine-socketio/src/com/fr/third/socketio/annotation/OnDisconnectScanner.java

@ -0,0 +1,66 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import com.fr.third.socketio.SocketIOClient;
import com.fr.third.socketio.handler.SocketIOException;
import com.fr.third.socketio.listener.DisconnectListener;
import com.fr.third.socketio.namespace.Namespace;
public class OnDisconnectScanner implements AnnotationScanner {
@Override
public Class<? extends Annotation> getScanAnnotation() {
return OnDisconnect.class;
}
@Override
public void addListener(Namespace namespace, final Object object, final Method method, Annotation annotation) {
namespace.addDisconnectListener(new DisconnectListener() {
@Override
public void onDisconnect(SocketIOClient client) {
try {
method.invoke(object, client);
} catch (InvocationTargetException e) {
throw new SocketIOException(e.getCause());
} catch (Exception e) {
throw new SocketIOException(e);
}
}
});
}
@Override
public void validate(Method method, Class<?> clazz) {
if (method.getParameterTypes().length != 1) {
throw new IllegalArgumentException("Wrong OnDisconnect listener signature: " + clazz + "." + method.getName());
}
boolean valid = false;
for (Class<?> eventType : method.getParameterTypes()) {
if (eventType.equals(SocketIOClient.class)) {
valid = true;
}
}
if (!valid) {
throw new IllegalArgumentException("Wrong OnDisconnect listener signature: " + clazz + "." + method.getName());
}
}
}

45
fine-socketio/src/com/fr/third/socketio/annotation/OnEvent.java

@ -0,0 +1,45 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation that defines <b>Event</b> handler.
* The value is required, and represents event name.
*
* Arguments in method:
*
* - SocketIOClient (optional)
* - AckRequest (optional)
* - Event data (optional)
*
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OnEvent {
/**
* Event name
*
* @return value
*/
String value();
}

154
fine-socketio/src/com/fr/third/socketio/annotation/OnEventScanner.java

@ -0,0 +1,154 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import com.fr.third.socketio.AckRequest;
import com.fr.third.socketio.MultiTypeArgs;
import com.fr.third.socketio.SocketIOClient;
import com.fr.third.socketio.handler.SocketIOException;
import com.fr.third.socketio.listener.DataListener;
import com.fr.third.socketio.listener.MultiTypeEventListener;
import com.fr.third.socketio.namespace.Namespace;
public class OnEventScanner implements AnnotationScanner {
@Override
public Class<? extends Annotation> getScanAnnotation() {
return OnEvent.class;
}
@Override
@SuppressWarnings("unchecked")
public void addListener(Namespace namespace, final Object object, final Method method, Annotation annot) {
OnEvent annotation = (OnEvent) annot;
if (annotation.value() == null || annotation.value().trim().length() == 0) {
throw new IllegalArgumentException("OnEvent \"value\" parameter is required");
}
final int socketIOClientIndex = paramIndex(method, SocketIOClient.class);
final int ackRequestIndex = paramIndex(method, AckRequest.class);
final List<Integer> dataIndexes = dataIndexes(method);
if (dataIndexes.size() > 1) {
List<Class<?>> classes = new ArrayList<Class<?>>();
for (int index : dataIndexes) {
Class<?> param = method.getParameterTypes()[index];
classes.add(param);
}
namespace.addMultiTypeEventListener(annotation.value(), new MultiTypeEventListener() {
@Override
public void onData(SocketIOClient client, MultiTypeArgs data, AckRequest ackSender) {
try {
Object[] args = new Object[method.getParameterTypes().length];
if (socketIOClientIndex != -1) {
args[socketIOClientIndex] = client;
}
if (ackRequestIndex != -1) {
args[ackRequestIndex] = ackSender;
}
int i = 0;
for (int index : dataIndexes) {
args[index] = data.get(i);
i++;
}
method.invoke(object, args);
} catch (InvocationTargetException e) {
throw new SocketIOException(e.getCause());
} catch (Exception e) {
throw new SocketIOException(e);
}
}
}, classes.toArray(new Class[classes.size()]));
} else {
Class objectType = Void.class;
if (!dataIndexes.isEmpty()) {
objectType = method.getParameterTypes()[dataIndexes.iterator().next()];
}
namespace.addEventListener(annotation.value(), objectType, new DataListener<Object>() {
@Override
public void onData(SocketIOClient client, Object data, AckRequest ackSender) {
try {
Object[] args = new Object[method.getParameterTypes().length];
if (socketIOClientIndex != -1) {
args[socketIOClientIndex] = client;
}
if (ackRequestIndex != -1) {
args[ackRequestIndex] = ackSender;
}
if (!dataIndexes.isEmpty()) {
int dataIndex = dataIndexes.iterator().next();
args[dataIndex] = data;
}
method.invoke(object, args);
} catch (InvocationTargetException e) {
throw new SocketIOException(e.getCause());
} catch (Exception e) {
throw new SocketIOException(e);
}
}
});
}
}
private List<Integer> dataIndexes(Method method) {
List<Integer> result = new ArrayList<Integer>();
int index = 0;
for (Class<?> type : method.getParameterTypes()) {
if (!type.equals(AckRequest.class) && !type.equals(SocketIOClient.class)) {
result.add(index);
}
index++;
}
return result;
}
private int paramIndex(Method method, Class<?> clazz) {
int index = 0;
for (Class<?> type : method.getParameterTypes()) {
if (type.equals(clazz)) {
return index;
}
index++;
}
return -1;
}
@Override
public void validate(Method method, Class<?> clazz) {
int paramsCount = method.getParameterTypes().length;
final int socketIOClientIndex = paramIndex(method, SocketIOClient.class);
final int ackRequestIndex = paramIndex(method, AckRequest.class);
List<Integer> dataIndexes = dataIndexes(method);
paramsCount -= dataIndexes.size();
if (socketIOClientIndex != -1) {
paramsCount--;
}
if (ackRequestIndex != -1) {
paramsCount--;
}
if (paramsCount != 0) {
throw new IllegalArgumentException("Wrong OnEvent listener signature: " + clazz + "." + method.getName());
}
}
}

105
fine-socketio/src/com/fr/third/socketio/annotation/ScannerEngine.java

@ -0,0 +1,105 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fr.third.socketio.namespace.Namespace;
public class ScannerEngine {
private static final Logger log = LoggerFactory.getLogger(ScannerEngine.class);
private static final List<? extends AnnotationScanner> annotations =
Arrays.asList(new OnConnectScanner(), new OnDisconnectScanner(), new OnEventScanner());
private Method findSimilarMethod(Class<?> objectClazz, Method method) {
Method[] methods = objectClazz.getDeclaredMethods();
for (Method m : methods) {
if (isEquals(m, method)) {
return m;
}
}
return null;
}
public void scan(Namespace namespace, Object object, Class<?> clazz)
throws IllegalArgumentException {
Method[] methods = clazz.getDeclaredMethods();
if (!clazz.isAssignableFrom(object.getClass())) {
for (Method method : methods) {
for (AnnotationScanner annotationScanner : annotations) {
Annotation ann = method.getAnnotation(annotationScanner.getScanAnnotation());
if (ann != null) {
annotationScanner.validate(method, clazz);
Method m = findSimilarMethod(object.getClass(), method);
if (m != null) {
annotationScanner.addListener(namespace, object, m, ann);
} else {
log.warn("Method similar to " + method.getName() + " can't be found in " + object.getClass());
}
}
}
}
} else {
for (Method method : methods) {
for (AnnotationScanner annotationScanner : annotations) {
Annotation ann = method.getAnnotation(annotationScanner.getScanAnnotation());
if (ann != null) {
annotationScanner.validate(method, clazz);
makeAccessible(method);
annotationScanner.addListener(namespace, object, method, ann);
}
}
}
if (clazz.getSuperclass() != null) {
scan(namespace, object, clazz.getSuperclass());
} else if (clazz.isInterface()) {
for (Class<?> superIfc : clazz.getInterfaces()) {
scan(namespace, object, superIfc);
}
}
}
}
private boolean isEquals(Method method1, Method method2) {
if (!method1.getName().equals(method2.getName())
|| !method1.getReturnType().equals(method2.getReturnType())) {
return false;
}
return Arrays.equals(method1.getParameterTypes(), method2.getParameterTypes());
}
private void makeAccessible(Method method) {
if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers()))
&& !method.isAccessible()) {
method.setAccessible(true);
}
}
}

88
fine-socketio/src/com/fr/third/socketio/annotation/SpringAnnotationScanner.java

@ -0,0 +1,88 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import com.fr.third.socketio.SocketIOServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fr.third.springframework.beans.BeansException;
import com.fr.third.springframework.beans.factory.config.BeanPostProcessor;
import com.fr.third.springframework.util.ReflectionUtils;
import com.fr.third.springframework.util.ReflectionUtils.MethodCallback;
import com.fr.third.springframework.util.ReflectionUtils.MethodFilter;
public class SpringAnnotationScanner implements BeanPostProcessor {
private static final Logger log = LoggerFactory.getLogger(SpringAnnotationScanner.class);
private final List<Class<? extends Annotation>> annotations =
Arrays.asList(OnConnect.class, OnDisconnect.class, OnEvent.class);
private final SocketIOServer socketIOServer;
private Class originalBeanClass;
public SpringAnnotationScanner(SocketIOServer socketIOServer) {
super();
this.socketIOServer = socketIOServer;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (originalBeanClass != null) {
socketIOServer.addListeners(bean, originalBeanClass);
log.info("{} bean listeners added", beanName);
originalBeanClass = null;
}
return bean;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
final AtomicBoolean add = new AtomicBoolean();
ReflectionUtils.doWithMethods(bean.getClass(),
new MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException,
IllegalAccessException {
add.set(true);
}
},
new MethodFilter() {
@Override
public boolean matches(Method method) {
for (Class<? extends Annotation> annotationClass : annotations) {
if (method.isAnnotationPresent(annotationClass)) {
return true;
}
}
return false;
}
});
if (add.get()) {
originalBeanClass = bean.getClass();
}
return bean;
}
}

256
fine-socketio/src/com/fr/third/socketio/handler/AuthorizeHandler.java

@ -0,0 +1,256 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.handler;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import com.fr.third.socketio.Configuration;
import com.fr.third.socketio.Disconnectable;
import com.fr.third.socketio.DisconnectableHub;
import com.fr.third.socketio.HandshakeData;
import com.fr.third.socketio.SocketIOClient;
import com.fr.third.socketio.Transport;
import com.fr.third.socketio.messages.HttpErrorMessage;
import com.fr.third.socketio.namespace.Namespace;
import com.fr.third.socketio.namespace.NamespacesHub;
import com.fr.third.socketio.protocol.AuthPacket;
import com.fr.third.socketio.protocol.Packet;
import com.fr.third.socketio.protocol.PacketType;
import com.fr.third.socketio.scheduler.CancelableScheduler;
import com.fr.third.socketio.scheduler.SchedulerKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fr.third.socketio.ack.AckManager;
import com.fr.third.socketio.store.StoreFactory;
import com.fr.third.socketio.store.pubsub.ConnectMessage;
import com.fr.third.socketio.store.pubsub.PubSubType;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.DefaultHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.handler.codec.http.cookie.Cookie;
import io.netty.handler.codec.http.cookie.ServerCookieDecoder;
@Sharable
public class AuthorizeHandler extends ChannelInboundHandlerAdapter implements Disconnectable {
private static final Logger log = LoggerFactory.getLogger(AuthorizeHandler.class);
private final CancelableScheduler disconnectScheduler;
private final String connectPath;
private final Configuration configuration;
private final NamespacesHub namespacesHub;
private final StoreFactory storeFactory;
private final DisconnectableHub disconnectable;
private final AckManager ackManager;
private final ClientsBox clientsBox;
public AuthorizeHandler(String connectPath, CancelableScheduler scheduler, Configuration configuration, NamespacesHub namespacesHub, StoreFactory storeFactory,
DisconnectableHub disconnectable, AckManager ackManager, ClientsBox clientsBox) {
super();
this.connectPath = connectPath;
this.configuration = configuration;
this.disconnectScheduler = scheduler;
this.namespacesHub = namespacesHub;
this.storeFactory = storeFactory;
this.disconnectable = disconnectable;
this.ackManager = ackManager;
this.clientsBox = clientsBox;
}
@Override
public void channelActive(final ChannelHandlerContext ctx) throws Exception {
SchedulerKey key = new SchedulerKey(SchedulerKey.Type.PING_TIMEOUT, ctx.channel());
disconnectScheduler.schedule(key, new Runnable() {
@Override
public void run() {
ctx.channel().close();
log.debug("Client with ip {} opened channel but doesn't send any data! Channel closed!", ctx.channel().remoteAddress());
}
}, configuration.getFirstDataTimeout(), TimeUnit.MILLISECONDS);
super.channelActive(ctx);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
SchedulerKey key = new SchedulerKey(SchedulerKey.Type.PING_TIMEOUT, ctx.channel());
disconnectScheduler.cancel(key);
if (msg instanceof FullHttpRequest) {
FullHttpRequest req = (FullHttpRequest) msg;
Channel channel = ctx.channel();
QueryStringDecoder queryDecoder = new QueryStringDecoder(req.uri());
if (!configuration.isAllowCustomRequests()
&& !queryDecoder.path().startsWith(connectPath)) {
HttpResponse res = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.BAD_REQUEST);
channel.writeAndFlush(res).addListener(ChannelFutureListener.CLOSE);
req.release();
return;
}
List<String> sid = queryDecoder.parameters().get("sid");
if (queryDecoder.path().equals(connectPath)
&& sid == null) {
String origin = req.headers().get(HttpHeaderNames.ORIGIN);
if (!authorize(ctx, channel, origin, queryDecoder.parameters(), req)) {
req.release();
return;
}
// forward message to polling or websocket handler to bind channel
}
}
ctx.fireChannelRead(msg);
}
private boolean authorize(ChannelHandlerContext ctx, Channel channel, String origin, Map<String, List<String>> params, FullHttpRequest req)
throws IOException {
Map<String, List<String>> headers = new HashMap<String, List<String>>(req.headers().names().size());
for (String name : req.headers().names()) {
List<String> values = req.headers().getAll(name);
headers.put(name, values);
}
HandshakeData data = new HandshakeData(req.headers(), params,
(InetSocketAddress)channel.remoteAddress(),
(InetSocketAddress)channel.localAddress(),
req.uri(), origin != null && !origin.equalsIgnoreCase("null"));
boolean result = false;
try {
result = configuration.getAuthorizationListener().isAuthorized(data);
} catch (Exception e) {
log.error("Authorization error", e);
}
if (!result) {
HttpResponse res = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.UNAUTHORIZED);
channel.writeAndFlush(res)
.addListener(ChannelFutureListener.CLOSE);
log.debug("Handshake unauthorized, query params: {} headers: {}", params, headers);
return false;
}
UUID sessionId = this.generateOrGetSessionIdFromRequest(req.headers());
List<String> transportValue = params.get("transport");
if (transportValue == null) {
log.error("Got no transports for request {}", req.uri());
HttpResponse res = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.UNAUTHORIZED);
channel.writeAndFlush(res).addListener(ChannelFutureListener.CLOSE);
return false;
}
Transport transport = Transport.byName(transportValue.get(0));
if (!configuration.getTransports().contains(transport)) {
Map<String, Object> errorData = new HashMap<String, Object>();
errorData.put("code", 0);
errorData.put("message", "Transport unknown");
channel.attr(EncoderHandler.ORIGIN).set(origin);
channel.writeAndFlush(new HttpErrorMessage(errorData));
return false;
}
ClientHead client = new ClientHead(sessionId, ackManager, disconnectable, storeFactory, data, clientsBox, transport, disconnectScheduler, configuration);
channel.attr(ClientHead.CLIENT).set(client);
clientsBox.addClient(client);
String[] transports = {};
if (configuration.getTransports().contains(Transport.WEBSOCKET)) {
transports = new String[] {"websocket"};
}
AuthPacket authPacket = new AuthPacket(sessionId, transports, configuration.getPingInterval(),
configuration.getPingTimeout());
Packet packet = new Packet(PacketType.OPEN);
packet.setData(authPacket);
client.send(packet);
client.schedulePingTimeout();
log.debug("Handshake authorized for sessionId: {}, query params: {} headers: {}", sessionId, params, headers);
return true;
}
/**
This method will either generate a new random sessionId or will retrieve the value stored
in the "io" cookie. Failures to parse will cause a logging warning to be generated and a
random uuid to be generated instead (same as not passing a cookie in the first place).
*/
private UUID generateOrGetSessionIdFromRequest(HttpHeaders headers) {
for (String cookieHeader: headers.getAll(HttpHeaderNames.COOKIE)) {
Set<Cookie> cookies = ServerCookieDecoder.LAX.decode(cookieHeader);
for (Cookie cookie : cookies) {
if (cookie.name().equals("io")) {
try {
return UUID.fromString(cookie.value());
} catch ( IllegalArgumentException iaex ) {
log.warn("Malformed UUID received for session! io=" + cookie.value());
}
}
}
}
return UUID.randomUUID();
}
public void connect(UUID sessionId) {
SchedulerKey key = new SchedulerKey(SchedulerKey.Type.PING_TIMEOUT, sessionId);
disconnectScheduler.cancel(key);
}
public void connect(ClientHead client) {
Namespace ns = namespacesHub.get(Namespace.DEFAULT_NAME);
if (!client.getNamespaces().contains(ns)) {
Packet packet = new Packet(PacketType.MESSAGE);
packet.setSubType(PacketType.CONNECT);
client.send(packet);
configuration.getStoreFactory().pubSubStore().publish(PubSubType.CONNECT, new ConnectMessage(client.getSessionId()));
SocketIOClient nsClient = client.addNamespaceClient(ns);
ns.onConnect(nsClient);
}
}
@Override
public void onDisconnect(ClientHead client) {
clientsBox.removeClient(client.getSessionId());
}
}

266
fine-socketio/src/com/fr/third/socketio/handler/ClientHead.java

@ -0,0 +1,266 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.handler;
import java.net.SocketAddress;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Queue;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import com.fr.third.socketio.Configuration;
import com.fr.third.socketio.DisconnectableHub;
import com.fr.third.socketio.HandshakeData;
import com.fr.third.socketio.Transport;
import com.fr.third.socketio.messages.OutPacketMessage;
import com.fr.third.socketio.namespace.Namespace;
import com.fr.third.socketio.protocol.Packet;
import com.fr.third.socketio.protocol.PacketType;
import com.fr.third.socketio.scheduler.CancelableScheduler;
import com.fr.third.socketio.scheduler.SchedulerKey;
import com.fr.third.socketio.transport.NamespaceClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fr.third.socketio.ack.AckManager;
import com.fr.third.socketio.store.Store;
import com.fr.third.socketio.store.StoreFactory;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.util.AttributeKey;
import io.netty.util.internal.PlatformDependent;
public class ClientHead {
private static final Logger log = LoggerFactory.getLogger(ClientHead.class);
public static final AttributeKey<ClientHead> CLIENT = AttributeKey.<ClientHead>valueOf("client");
private final AtomicBoolean disconnected = new AtomicBoolean();
private final Map<Namespace, NamespaceClient> namespaceClients = PlatformDependent.newConcurrentHashMap();
private final Map<Transport, TransportState> channels = new HashMap<Transport, TransportState>(2);
private final HandshakeData handshakeData;
private final UUID sessionId;
private final Store store;
private final DisconnectableHub disconnectableHub;
private final AckManager ackManager;
private ClientsBox clientsBox;
private final CancelableScheduler disconnectScheduler;
private final Configuration configuration;
private Packet lastBinaryPacket;
// TODO use lazy set
private volatile Transport currentTransport;
public ClientHead(UUID sessionId, AckManager ackManager, DisconnectableHub disconnectable,
StoreFactory storeFactory, HandshakeData handshakeData, ClientsBox clientsBox, Transport transport, CancelableScheduler disconnectScheduler,
Configuration configuration) {
this.sessionId = sessionId;
this.ackManager = ackManager;
this.disconnectableHub = disconnectable;
this.store = storeFactory.createStore(sessionId);
this.handshakeData = handshakeData;
this.clientsBox = clientsBox;
this.currentTransport = transport;
this.disconnectScheduler = disconnectScheduler;
this.configuration = configuration;
channels.put(Transport.POLLING, new TransportState());
channels.put(Transport.WEBSOCKET, new TransportState());
}
public void bindChannel(Channel channel, Transport transport) {
log.debug("binding channel: {} to transport: {}", channel, transport);
TransportState state = channels.get(transport);
Channel prevChannel = state.update(channel);
if (prevChannel != null) {
clientsBox.remove(prevChannel);
}
clientsBox.add(channel, this);
sendPackets(transport, channel);
}
public String getOrigin() {
return handshakeData.getHttpHeaders().get(HttpHeaderNames.ORIGIN);
}
public ChannelFuture send(Packet packet) {
return send(packet, getCurrentTransport());
}
public void cancelPingTimeout() {
SchedulerKey key = new SchedulerKey(SchedulerKey.Type.PING_TIMEOUT, sessionId);
disconnectScheduler.cancel(key);
}
public void schedulePingTimeout() {
SchedulerKey key = new SchedulerKey(SchedulerKey.Type.PING_TIMEOUT, sessionId);
disconnectScheduler.schedule(key, new Runnable() {
@Override
public void run() {
ClientHead client = clientsBox.get(sessionId);
if (client != null) {
client.onChannelDisconnect();
log.debug("{} removed due to ping timeout", sessionId);
}
}
}, configuration.getPingTimeout() + configuration.getPingInterval(), TimeUnit.MILLISECONDS);
}
public ChannelFuture send(Packet packet, Transport transport) {
TransportState state = channels.get(transport);
state.getPacketsQueue().add(packet);
Channel channel = state.getChannel();
if (channel == null
|| (transport == Transport.POLLING && channel.attr(EncoderHandler.WRITE_ONCE).get() != null)) {
return null;
}
return sendPackets(transport, channel);
}
private ChannelFuture sendPackets(Transport transport, Channel channel) {
return channel.writeAndFlush(new OutPacketMessage(this, transport));
}
public void removeNamespaceClient(NamespaceClient client) {
namespaceClients.remove(client.getNamespace());
if (namespaceClients.isEmpty()) {
disconnectableHub.onDisconnect(this);
}
}
public NamespaceClient getChildClient(Namespace namespace) {
return namespaceClients.get(namespace);
}
public NamespaceClient addNamespaceClient(Namespace namespace) {
NamespaceClient client = new NamespaceClient(this, namespace);
namespaceClients.put(namespace, client);
return client;
}
public Set<Namespace> getNamespaces() {
return namespaceClients.keySet();
}
public boolean isConnected() {
return !disconnected.get();
}
public void onChannelDisconnect() {
cancelPingTimeout();
disconnected.set(true);
for (NamespaceClient client : namespaceClients.values()) {
client.onDisconnect();
}
for (TransportState state : channels.values()) {
if (state.getChannel() != null) {
clientsBox.remove(state.getChannel());
}
}
}
public HandshakeData getHandshakeData() {
return handshakeData;
}
public AckManager getAckManager() {
return ackManager;
}
public UUID getSessionId() {
return sessionId;
}
public SocketAddress getRemoteAddress() {
return handshakeData.getAddress();
}
public void disconnect() {
ChannelFuture future = send(new Packet(PacketType.DISCONNECT));
future.addListener(ChannelFutureListener.CLOSE);
onChannelDisconnect();
}
public boolean isChannelOpen() {
for (TransportState state : channels.values()) {
if (state.getChannel() != null
&& state.getChannel().isActive()) {
return true;
}
}
return false;
}
public Store getStore() {
return store;
}
public boolean isTransportChannel(Channel channel, Transport transport) {
TransportState state = channels.get(transport);
if (state.getChannel() == null) {
return false;
}
return state.getChannel().equals(channel);
}
public void upgradeCurrentTransport(Transport currentTransport) {
TransportState state = channels.get(currentTransport);
for (Entry<Transport, TransportState> entry : channels.entrySet()) {
if (!entry.getKey().equals(currentTransport)) {
Queue<Packet> queue = entry.getValue().getPacketsQueue();
state.setPacketsQueue(queue);
sendPackets(currentTransport, state.getChannel());
this.currentTransport = currentTransport;
log.debug("Transport upgraded to: {} for: {}", currentTransport, sessionId);
break;
}
}
}
public Transport getCurrentTransport() {
return currentTransport;
}
public Queue<Packet> getPacketsQueue(Transport transport) {
return channels.get(transport).getPacketsQueue();
}
public void setLastBinaryPacket(Packet lastBinaryPacket) {
this.lastBinaryPacket = lastBinaryPacket;
}
public Packet getLastBinaryPacket() {
return lastBinaryPacket;
}
}

65
fine-socketio/src/com/fr/third/socketio/handler/ClientsBox.java

@ -0,0 +1,65 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.handler;
import com.fr.third.socketio.HandshakeData;
import io.netty.channel.Channel;
import io.netty.util.internal.PlatformDependent;
import java.util.Map;
import java.util.UUID;
public class ClientsBox {
private final Map<UUID, ClientHead> uuid2clients = PlatformDependent.newConcurrentHashMap();
private final Map<Channel, ClientHead> channel2clients = PlatformDependent.newConcurrentHashMap();
// TODO use storeFactory
public HandshakeData getHandshakeData(UUID sessionId) {
ClientHead client = uuid2clients.get(sessionId);
if (client == null) {
return null;
}
return client.getHandshakeData();
}
public void addClient(ClientHead clientHead) {
uuid2clients.put(clientHead.getSessionId(), clientHead);
}
public void removeClient(UUID sessionId) {
uuid2clients.remove(sessionId);
}
public ClientHead get(UUID sessionId) {
return uuid2clients.get(sessionId);
}
public void add(Channel channel, ClientHead clientHead) {
channel2clients.put(channel, clientHead);
}
public void remove(Channel channel) {
channel2clients.remove(channel);
}
public ClientHead get(Channel channel) {
return channel2clients.get(channel);
}
}

346
fine-socketio/src/com/fr/third/socketio/handler/EncoderHandler.java

@ -0,0 +1,346 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.handler;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Queue;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import com.fr.third.socketio.Configuration;
import com.fr.third.socketio.Transport;
import com.fr.third.socketio.messages.HttpErrorMessage;
import com.fr.third.socketio.messages.HttpMessage;
import com.fr.third.socketio.messages.OutPacketMessage;
import com.fr.third.socketio.messages.XHROptionsMessage;
import com.fr.third.socketio.messages.XHRPostMessage;
import com.fr.third.socketio.protocol.Packet;
import com.fr.third.socketio.protocol.PacketEncoder;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufOutputStream;
import io.netty.buffer.ByteBufUtil;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.http.DefaultHttpContent;
import io.netty.handler.codec.http.DefaultHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.util.Attribute;
import io.netty.util.AttributeKey;
import io.netty.util.CharsetUtil;
@Sharable
public class EncoderHandler extends ChannelOutboundHandlerAdapter {
private static final byte[] OK = "ok".getBytes(CharsetUtil.UTF_8);
public static final AttributeKey<String> ORIGIN = AttributeKey.valueOf("origin");
public static final AttributeKey<String> USER_AGENT = AttributeKey.valueOf("userAgent");
public static final AttributeKey<Boolean> B64 = AttributeKey.valueOf("b64");
public static final AttributeKey<Integer> JSONP_INDEX = AttributeKey.valueOf("jsonpIndex");
public static final AttributeKey<Boolean> WRITE_ONCE = AttributeKey.valueOf("writeOnce");
private static final Logger log = LoggerFactory.getLogger(EncoderHandler.class);
private final PacketEncoder encoder;
private String version;
private Configuration configuration;
public EncoderHandler(Configuration configuration, PacketEncoder encoder) throws IOException {
this.encoder = encoder;
this.configuration = configuration;
if (configuration.isAddVersionHeader()) {
readVersion();
}
}
private void readVersion() throws IOException {
Enumeration<URL> resources = getClass().getClassLoader().getResources("META-INF/MANIFEST.MF");
while (resources.hasMoreElements()) {
try {
Manifest manifest = new Manifest(resources.nextElement().openStream());
Attributes attrs = manifest.getMainAttributes();
if (attrs == null) {
continue;
}
String name = attrs.getValue("Bundle-Name");
if (name != null && name.equals("netty-socketio")) {
version = name + "/" + attrs.getValue("Bundle-Version");
break;
}
} catch (IOException E) {
// skip it
}
}
}
private void write(XHROptionsMessage msg, ChannelHandlerContext ctx, ChannelPromise promise) {
HttpResponse res = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.OK);
res.headers().add(HttpHeaderNames.SET_COOKIE, "io=" + msg.getSessionId())
.add(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE)
.add(HttpHeaderNames.ACCESS_CONTROL_ALLOW_HEADERS, HttpHeaderNames.CONTENT_TYPE);
String origin = ctx.channel().attr(ORIGIN).get();
addOriginHeaders(origin, res);
ByteBuf out = encoder.allocateBuffer(ctx.alloc());
sendMessage(msg, ctx.channel(), out, res, promise);
}
private void write(XHRPostMessage msg, ChannelHandlerContext ctx, ChannelPromise promise) {
ByteBuf out = encoder.allocateBuffer(ctx.alloc());
out.writeBytes(OK);
sendMessage(msg, ctx.channel(), out, "text/html", promise, HttpResponseStatus.OK);
}
private void sendMessage(HttpMessage msg, Channel channel, ByteBuf out, String type, ChannelPromise promise, HttpResponseStatus status) {
HttpResponse res = new DefaultHttpResponse(HTTP_1_1, status);
res.headers().add(HttpHeaderNames.CONTENT_TYPE, type)
.add(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
if (msg.getSessionId() != null) {
res.headers().add(HttpHeaderNames.SET_COOKIE, "io=" + msg.getSessionId());
}
String origin = channel.attr(ORIGIN).get();
addOriginHeaders(origin, res);
HttpUtil.setContentLength(res, out.readableBytes());
// prevent XSS warnings on IE
// https://github.com/LearnBoost/socket.io/pull/1333
String userAgent = channel.attr(EncoderHandler.USER_AGENT).get();
if (userAgent != null && (userAgent.contains(";MSIE") || userAgent.contains("Trident/"))) {
res.headers().add("X-XSS-Protection", "0");
}
sendMessage(msg, channel, out, res, promise);
}
private void sendMessage(HttpMessage msg, Channel channel, ByteBuf out, HttpResponse res, ChannelPromise promise) {
channel.write(res);
if (log.isTraceEnabled()) {
if (msg.getSessionId() != null) {
log.trace("Out message: {} - sessionId: {}", out.toString(CharsetUtil.UTF_8), msg.getSessionId());
} else {
log.trace("Out message: {}", out.toString(CharsetUtil.UTF_8));
}
}
if (out.isReadable()) {
channel.write(new DefaultHttpContent(out));
} else {
out.release();
}
channel.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT, promise).addListener(ChannelFutureListener.CLOSE);
}
private void sendError(HttpErrorMessage errorMsg, ChannelHandlerContext ctx, ChannelPromise promise) throws IOException {
final ByteBuf encBuf = encoder.allocateBuffer(ctx.alloc());
ByteBufOutputStream out = new ByteBufOutputStream(encBuf);
encoder.getJsonSupport().writeValue(out, errorMsg.getData());
sendMessage(errorMsg, ctx.channel(), encBuf, "application/json", promise, HttpResponseStatus.BAD_REQUEST);
}
private void addOriginHeaders(String origin, HttpResponse res) {
if (version != null) {
res.headers().add(HttpHeaderNames.SERVER, version);
}
if (configuration.getOrigin() != null) {
res.headers().add(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN, configuration.getOrigin());
res.headers().add(HttpHeaderNames.ACCESS_CONTROL_ALLOW_CREDENTIALS, Boolean.TRUE);
} else {
if (origin != null) {
res.headers().add(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN, origin);
res.headers().add(HttpHeaderNames.ACCESS_CONTROL_ALLOW_CREDENTIALS, Boolean.TRUE);
} else {
res.headers().add(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
}
}
}
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
if (!(msg instanceof HttpMessage)) {
super.write(ctx, msg, promise);
return;
}
if (msg instanceof OutPacketMessage) {
OutPacketMessage m = (OutPacketMessage) msg;
if (m.getTransport() == Transport.WEBSOCKET) {
handleWebsocket((OutPacketMessage) msg, ctx, promise);
}
if (m.getTransport() == Transport.POLLING) {
handleHTTP((OutPacketMessage) msg, ctx, promise);
}
} else if (msg instanceof XHROptionsMessage) {
write((XHROptionsMessage) msg, ctx, promise);
} else if (msg instanceof XHRPostMessage) {
write((XHRPostMessage) msg, ctx, promise);
} else if (msg instanceof HttpErrorMessage) {
sendError((HttpErrorMessage) msg, ctx, promise);
}
}
private void handleWebsocket(final OutPacketMessage msg, ChannelHandlerContext ctx, ChannelPromise promise) throws IOException {
ChannelFutureList writeFutureList = new ChannelFutureList();
while (true) {
Queue<Packet> queue = msg.getClientHead().getPacketsQueue(msg.getTransport());
Packet packet = queue.poll();
if (packet == null) {
writeFutureList.setChannelPromise(promise);
break;
}
final ByteBuf out = encoder.allocateBuffer(ctx.alloc());
encoder.encodePacket(packet, out, ctx.alloc(), true);
WebSocketFrame res = new TextWebSocketFrame(out);
if (log.isTraceEnabled()) {
log.trace("Out message: {} sessionId: {}", out.toString(CharsetUtil.UTF_8), msg.getSessionId());
}
if (out.isReadable()) {
writeFutureList.add(ctx.channel().writeAndFlush(res));
} else {
out.release();
}
for (ByteBuf buf : packet.getAttachments()) {
ByteBuf outBuf = encoder.allocateBuffer(ctx.alloc());
outBuf.writeByte(4);
outBuf.writeBytes(buf);
if (log.isTraceEnabled()) {
log.trace("Out attachment: {} sessionId: {}", ByteBufUtil.hexDump(outBuf), msg.getSessionId());
}
writeFutureList.add(ctx.channel().writeAndFlush(new BinaryWebSocketFrame(outBuf)));
}
}
}
private void handleHTTP(OutPacketMessage msg, ChannelHandlerContext ctx, ChannelPromise promise) throws IOException {
Channel channel = ctx.channel();
Attribute<Boolean> attr = channel.attr(WRITE_ONCE);
Queue<Packet> queue = msg.getClientHead().getPacketsQueue(msg.getTransport());
if (!channel.isActive() || queue.isEmpty() || !attr.compareAndSet(null, true)) {
promise.trySuccess();
return;
}
ByteBuf out = encoder.allocateBuffer(ctx.alloc());
Boolean b64 = ctx.channel().attr(EncoderHandler.B64).get();
if (b64 != null && b64) {
Integer jsonpIndex = ctx.channel().attr(EncoderHandler.JSONP_INDEX).get();
encoder.encodeJsonP(jsonpIndex, queue, out, ctx.alloc(), 50);
String type = "application/javascript";
if (jsonpIndex == null) {
type = "text/plain";
}
sendMessage(msg, channel, out, type, promise, HttpResponseStatus.OK);
} else {
encoder.encodePackets(queue, out, ctx.alloc(), 50);
sendMessage(msg, channel, out, "application/octet-stream", promise, HttpResponseStatus.OK);
}
}
/**
* Helper class for the handleWebsocket method, handles a list of ChannelFutures and
* sets the status of a promise when
* - any of the operations fail
* - all of the operations succeed
* The setChannelPromise method should be called after all the futures are added
*/
private class ChannelFutureList implements GenericFutureListener<Future<Void>> {
private List<ChannelFuture> futureList = new ArrayList<ChannelFuture>();
private ChannelPromise promise = null;
private void cleanup() {
promise = null;
for (ChannelFuture f : futureList) f.removeListener(this);
}
private void validate() {
boolean allSuccess = true;
for (ChannelFuture f : futureList) {
if (f.isDone()) {
if (!f.isSuccess()) {
promise.tryFailure(f.cause());
cleanup();
return;
}
}
else {
allSuccess = false;
}
}
if (allSuccess) {
promise.trySuccess();
cleanup();
}
}
public void add(ChannelFuture f) {
futureList.add(f);
f.addListener(this);
}
public void setChannelPromise(ChannelPromise p) {
promise = p;
validate();
}
@Override
public void operationComplete(Future<Void> voidFuture) throws Exception {
if (promise != null) validate();
}
}
}

107
fine-socketio/src/com/fr/third/socketio/handler/InPacketHandler.java

@ -0,0 +1,107 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.handler;
import com.fr.third.socketio.listener.ExceptionListener;
import com.fr.third.socketio.messages.PacketsMessage;
import com.fr.third.socketio.namespace.Namespace;
import com.fr.third.socketio.namespace.NamespacesHub;
import com.fr.third.socketio.protocol.Packet;
import com.fr.third.socketio.protocol.PacketDecoder;
import com.fr.third.socketio.protocol.PacketType;
import com.fr.third.socketio.transport.NamespaceClient;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Sharable
public class InPacketHandler extends SimpleChannelInboundHandler<PacketsMessage> {
private static final Logger log = LoggerFactory.getLogger(InPacketHandler.class);
private final PacketListener packetListener;
private final PacketDecoder decoder;
private final NamespacesHub namespacesHub;
private final ExceptionListener exceptionListener;
public InPacketHandler(PacketListener packetListener, PacketDecoder decoder, NamespacesHub namespacesHub, ExceptionListener exceptionListener) {
super();
this.packetListener = packetListener;
this.decoder = decoder;
this.namespacesHub = namespacesHub;
this.exceptionListener = exceptionListener;
}
@Override
protected void channelRead0(io.netty.channel.ChannelHandlerContext ctx, PacketsMessage message)
throws Exception {
ByteBuf content = message.getContent();
ClientHead client = message.getClient();
if (log.isTraceEnabled()) {
log.trace("In message: {} sessionId: {}", content.toString(CharsetUtil.UTF_8), client.getSessionId());
}
while (content.isReadable()) {
try {
Packet packet = decoder.decodePackets(content, client);
if (packet.hasAttachments() && !packet.isAttachmentsLoaded()) {
return;
}
Namespace ns = namespacesHub.get(packet.getNsp());
if (ns == null) {
if (packet.getSubType() == PacketType.CONNECT) {
Packet p = new Packet(PacketType.MESSAGE);
p.setSubType(PacketType.ERROR);
p.setNsp(packet.getNsp());
p.setData("Invalid namespace");
client.send(p);
return;
}
log.debug("Can't find namespace for endpoint: {}, sessionId: {} probably it was removed.", packet.getNsp(), client.getSessionId());
return;
}
if (packet.getSubType() == PacketType.CONNECT) {
client.addNamespaceClient(ns);
}
NamespaceClient nClient = client.getChildClient(ns);
if (nClient == null) {
log.debug("Can't find namespace client in namespace: {}, sessionId: {} probably it was disconnected.", ns.getName(), client.getSessionId());
return;
}
packetListener.onPacket(packet, nClient, message.getTransport());
} catch (Exception ex) {
String c = content.toString(CharsetUtil.UTF_8);
log.error("Error during data processing. Client sessionId: " + client.getSessionId() + ", data: " + c, ex);
throw ex;
}
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable e) throws Exception {
if (!exceptionListener.exceptionCaught(ctx, e)) {
super.exceptionCaught(ctx, e);
}
}
}

120
fine-socketio/src/com/fr/third/socketio/handler/PacketListener.java

@ -0,0 +1,120 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.handler;
import java.util.Collections;
import java.util.List;
import com.fr.third.socketio.AckRequest;
import com.fr.third.socketio.Transport;
import com.fr.third.socketio.ack.AckManager;
import com.fr.third.socketio.namespace.Namespace;
import com.fr.third.socketio.namespace.NamespacesHub;
import com.fr.third.socketio.protocol.Packet;
import com.fr.third.socketio.protocol.PacketType;
import com.fr.third.socketio.scheduler.CancelableScheduler;
import com.fr.third.socketio.scheduler.SchedulerKey;
import com.fr.third.socketio.transport.NamespaceClient;
import com.fr.third.socketio.transport.PollingTransport;
public class PacketListener {
private final NamespacesHub namespacesHub;
private final AckManager ackManager;
private final CancelableScheduler scheduler;
public PacketListener(AckManager ackManager, NamespacesHub namespacesHub, PollingTransport xhrPollingTransport,
CancelableScheduler scheduler) {
this.ackManager = ackManager;
this.namespacesHub = namespacesHub;
this.scheduler = scheduler;
}
public void onPacket(Packet packet, NamespaceClient client, Transport transport) {
final AckRequest ackRequest = new AckRequest(packet, client);
if (packet.isAckRequested()) {
ackManager.initAckIndex(client.getSessionId(), packet.getAckId());
}
switch (packet.getType()) {
case PING: {
Packet outPacket = new Packet(PacketType.PONG);
outPacket.setData(packet.getData());
// TODO use future
client.getBaseClient().send(outPacket, transport);
if ("probe".equals(packet.getData())) {
client.getBaseClient().send(new Packet(PacketType.NOOP), Transport.POLLING);
} else {
client.getBaseClient().schedulePingTimeout();
}
Namespace namespace = namespacesHub.get(packet.getNsp());
namespace.onPing(client);
break;
}
case UPGRADE: {
client.getBaseClient().schedulePingTimeout();
SchedulerKey key = new SchedulerKey(SchedulerKey.Type.UPGRADE_TIMEOUT, client.getSessionId());
scheduler.cancel(key);
client.getBaseClient().upgradeCurrentTransport(transport);
break;
}
case MESSAGE: {
client.getBaseClient().schedulePingTimeout();
if (packet.getSubType() == PacketType.DISCONNECT) {
client.onDisconnect();
}
if (packet.getSubType() == PacketType.CONNECT) {
Namespace namespace = namespacesHub.get(packet.getNsp());
namespace.onConnect(client);
// send connect handshake packet back to client
client.getBaseClient().send(packet, transport);
}
if (packet.getSubType() == PacketType.ACK
|| packet.getSubType() == PacketType.BINARY_ACK) {
ackManager.onAck(client, packet);
}
if (packet.getSubType() == PacketType.EVENT
|| packet.getSubType() == PacketType.BINARY_EVENT) {
Namespace namespace = namespacesHub.get(packet.getNsp());
List<Object> args = Collections.emptyList();
if (packet.getData() != null) {
args = packet.getData();
}
namespace.onEvent(client, packet.getName(), args, ackRequest);
}
break;
}
case CLOSE:
client.getBaseClient().onChannelDisconnect();
break;
default:
break;
}
}
}

34
fine-socketio/src/com/fr/third/socketio/handler/SocketIOException.java

@ -0,0 +1,34 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.handler;
public class SocketIOException extends RuntimeException {
private static final long serialVersionUID = -9218908839842557188L;
public SocketIOException(String message, Throwable cause) {
super(message, cause);
}
public SocketIOException(String message) {
super(message);
}
public SocketIOException(Throwable cause) {
super(cause);
}
}

28
fine-socketio/src/com/fr/third/socketio/handler/SuccessAuthorizationListener.java

@ -0,0 +1,28 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.handler;
import com.fr.third.socketio.AuthorizationListener;
import com.fr.third.socketio.HandshakeData;
public class SuccessAuthorizationListener implements AuthorizationListener {
@Override
public boolean isAuthorized(HandshakeData data) {
return true;
}
}

48
fine-socketio/src/com/fr/third/socketio/handler/TransportState.java

@ -0,0 +1,48 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.handler;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import com.fr.third.socketio.protocol.Packet;
import io.netty.channel.Channel;
public class TransportState {
private Queue<Packet> packetsQueue = new ConcurrentLinkedQueue<Packet>();
private Channel channel;
public void setPacketsQueue(Queue<Packet> packetsQueue) {
this.packetsQueue = packetsQueue;
}
public Queue<Packet> getPacketsQueue() {
return packetsQueue;
}
public Channel getChannel() {
return channel;
}
public Channel update(Channel channel) {
Channel prevChannel = this.channel;
this.channel = channel;
return prevChannel;
}
}

57
fine-socketio/src/com/fr/third/socketio/handler/WrongUrlHandler.java

@ -0,0 +1,57 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.handler;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.DefaultHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.QueryStringDecoder;
@Sharable
public class WrongUrlHandler extends ChannelInboundHandlerAdapter {
private static final Logger log = LoggerFactory.getLogger(WrongUrlHandler.class);
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof FullHttpRequest) {
FullHttpRequest req = (FullHttpRequest) msg;
Channel channel = ctx.channel();
QueryStringDecoder queryDecoder = new QueryStringDecoder(req.uri());
HttpResponse res = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.BAD_REQUEST);
ChannelFuture f = channel.writeAndFlush(res);
f.addListener(ChannelFutureListener.CLOSE);
req.release();
log.warn("Blocked wrong socket.io-context request! url: {}, params: {}, ip: {}", queryDecoder.path(), queryDecoder.parameters(), channel.remoteAddress());
return;
}
super.channelRead(ctx, msg);
}
}

36
fine-socketio/src/com/fr/third/socketio/listener/ClientListeners.java

@ -0,0 +1,36 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.listener;
public interface ClientListeners {
void addMultiTypeEventListener(String eventName, MultiTypeEventListener listener, Class<?> ... eventClass);
<T> void addEventListener(String eventName, Class<T> eventClass, DataListener<T> listener);
void addDisconnectListener(DisconnectListener listener);
void addConnectListener(ConnectListener listener);
void addPingListener(PingListener listener);
void addListeners(Object listeners);
void addListeners(Object listeners, Class<?> listenersClass);
void removeAllListeners(String eventName);
}

24
fine-socketio/src/com/fr/third/socketio/listener/ConnectListener.java

@ -0,0 +1,24 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.listener;
import com.fr.third.socketio.SocketIOClient;
public interface ConnectListener {
void onConnect(SocketIOClient client);
}

34
fine-socketio/src/com/fr/third/socketio/listener/DataListener.java

@ -0,0 +1,34 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.listener;
import com.fr.third.socketio.AckRequest;
import com.fr.third.socketio.SocketIOClient;
public interface DataListener<T> {
/**
* Invokes when data object received from client
*
* @param client - receiver
* @param data - received object
* @param ackSender - ack request
*
* @throws Exception
*/
void onData(SocketIOClient client, T data, AckRequest ackSender) throws Exception;
}

56
fine-socketio/src/com/fr/third/socketio/listener/DefaultExceptionListener.java

@ -0,0 +1,56 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.listener;
import com.fr.third.socketio.SocketIOClient;
import io.netty.channel.ChannelHandlerContext;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DefaultExceptionListener extends ExceptionListenerAdapter {
private static final Logger log = LoggerFactory.getLogger(DefaultExceptionListener.class);
@Override
public void onEventException(Exception e, List<Object> args, SocketIOClient client) {
log.error(e.getMessage(), e);
}
@Override
public void onDisconnectException(Exception e, SocketIOClient client) {
log.error(e.getMessage(), e);
}
@Override
public void onConnectException(Exception e, SocketIOClient client) {
log.error(e.getMessage(), e);
}
@Override
public void onPingException(Exception e, SocketIOClient client) {
log.error(e.getMessage(), e);
}
@Override
public boolean exceptionCaught(ChannelHandlerContext ctx, Throwable e) throws Exception {
log.error(e.getMessage(), e);
return true;
}
}

24
fine-socketio/src/com/fr/third/socketio/listener/DisconnectListener.java

@ -0,0 +1,24 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.listener;
import com.fr.third.socketio.SocketIOClient;
public interface DisconnectListener {
void onDisconnect(SocketIOClient client);
}

35
fine-socketio/src/com/fr/third/socketio/listener/ExceptionListener.java

@ -0,0 +1,35 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.listener;
import com.fr.third.socketio.SocketIOClient;
import io.netty.channel.ChannelHandlerContext;
import java.util.List;
public interface ExceptionListener {
void onEventException(Exception e, List<Object> args, SocketIOClient client);
void onDisconnectException(Exception e, SocketIOClient client);
void onConnectException(Exception e, SocketIOClient client);
void onPingException(Exception e, SocketIOClient client);
boolean exceptionCaught(ChannelHandlerContext ctx, Throwable e) throws Exception;
}

47
fine-socketio/src/com/fr/third/socketio/listener/ExceptionListenerAdapter.java

@ -0,0 +1,47 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.listener;
import com.fr.third.socketio.SocketIOClient;
import io.netty.channel.ChannelHandlerContext;
import java.util.List;
/**
* Base callback exceptions listener
*
*
*/
public abstract class ExceptionListenerAdapter implements ExceptionListener {
@Override
public void onEventException(Exception e, List<Object> data, SocketIOClient client) {
}
@Override
public void onDisconnectException(Exception e, SocketIOClient client) {
}
@Override
public void onConnectException(Exception e, SocketIOClient client) {
}
@Override
public boolean exceptionCaught(ChannelHandlerContext ctx, Throwable e) throws Exception {
return false;
}
}

26
fine-socketio/src/com/fr/third/socketio/listener/MultiTypeEventListener.java

@ -0,0 +1,26 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.listener;
import com.fr.third.socketio.MultiTypeArgs;
/**
* Multi type args event listener
*
*/
public interface MultiTypeEventListener extends DataListener<MultiTypeArgs> {
}

24
fine-socketio/src/com/fr/third/socketio/listener/PingListener.java

@ -0,0 +1,24 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.listener;
import com.fr.third.socketio.SocketIOClient;
public interface PingListener {
void onPing(SocketIOClient client);
}

33
fine-socketio/src/com/fr/third/socketio/messages/HttpErrorMessage.java

@ -0,0 +1,33 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.messages;
import java.util.Map;
public class HttpErrorMessage extends HttpMessage {
private final Map<String, Object> data;
public HttpErrorMessage(Map<String, Object> data) {
super(null, null);
this.data = data;
}
public Map<String, Object> getData() {
return data;
}
}

38
fine-socketio/src/com/fr/third/socketio/messages/HttpMessage.java

@ -0,0 +1,38 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.messages;
import java.util.UUID;
public abstract class HttpMessage {
private final String origin;
private final UUID sessionId;
public HttpMessage(String origin, UUID sessionId) {
this.origin = origin;
this.sessionId = sessionId;
}
public String getOrigin() {
return origin;
}
public UUID getSessionId() {
return sessionId;
}
}

41
fine-socketio/src/com/fr/third/socketio/messages/OutPacketMessage.java

@ -0,0 +1,41 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.messages;
import com.fr.third.socketio.Transport;
import com.fr.third.socketio.handler.ClientHead;
public class OutPacketMessage extends HttpMessage {
private final ClientHead clientHead;
private final Transport transport;
public OutPacketMessage(ClientHead clientHead, Transport transport) {
super(clientHead.getOrigin(), clientHead.getSessionId());
this.clientHead = clientHead;
this.transport = transport;
}
public Transport getTransport() {
return transport;
}
public ClientHead getClientHead() {
return clientHead;
}
}

47
fine-socketio/src/com/fr/third/socketio/messages/PacketsMessage.java

@ -0,0 +1,47 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.messages;
import com.fr.third.socketio.Transport;
import io.netty.buffer.ByteBuf;
import com.fr.third.socketio.handler.ClientHead;
public class PacketsMessage {
private final ClientHead client;
private final ByteBuf content;
private final Transport transport;
public PacketsMessage(ClientHead client, ByteBuf content, Transport transport) {
this.client = client;
this.content = content;
this.transport = transport;
}
public Transport getTransport() {
return transport;
}
public ClientHead getClient() {
return client;
}
public ByteBuf getContent() {
return content;
}
}

26
fine-socketio/src/com/fr/third/socketio/messages/XHROptionsMessage.java

@ -0,0 +1,26 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.messages;
import java.util.UUID;
public class XHROptionsMessage extends XHRPostMessage {
public XHROptionsMessage(String origin, UUID sessionId) {
super(origin, sessionId);
}
}

26
fine-socketio/src/com/fr/third/socketio/messages/XHRPostMessage.java

@ -0,0 +1,26 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.messages;
import java.util.UUID;
public class XHRPostMessage extends HttpMessage {
public XHRPostMessage(String origin, UUID sessionId) {
super(origin, sessionId);
}
}

86
fine-socketio/src/com/fr/third/socketio/misc/CompositeIterable.java

@ -0,0 +1,86 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.misc;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class CompositeIterable<T> implements Iterable<T>, Iterator<T> {
private List<Iterable<T>> iterablesList;
private Iterable<T>[] iterables;
private Iterator<Iterator<T>> listIterator;
private Iterator<T> currentIterator;
public CompositeIterable(List<Iterable<T>> iterables) {
this.iterablesList = iterables;
}
public CompositeIterable(Iterable<T> ... iterables) {
this.iterables = iterables;
}
public CompositeIterable(CompositeIterable<T> iterable) {
this.iterables = iterable.iterables;
this.iterablesList = iterable.iterablesList;
}
@Override
public Iterator<T> iterator() {
List<Iterator<T>> iterators = new ArrayList<Iterator<T>>();
if (iterables != null) {
for (Iterable<T> iterable : iterables) {
iterators.add(iterable.iterator());
}
} else {
for (Iterable<T> iterable : iterablesList) {
iterators.add(iterable.iterator());
}
}
listIterator = iterators.iterator();
currentIterator = null;
return this;
}
@Override
public boolean hasNext() {
if (currentIterator == null || !currentIterator.hasNext()) {
while (listIterator.hasNext()) {
Iterator<T> iterator = listIterator.next();
if (iterator.hasNext()) {
currentIterator = iterator;
return true;
}
}
return false;
}
return currentIterator.hasNext();
}
@Override
public T next() {
hasNext();
return currentIterator.next();
}
@Override
public void remove() {
currentIterator.remove();
}
}

49
fine-socketio/src/com/fr/third/socketio/misc/IterableCollection.java

@ -0,0 +1,49 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.misc;
import java.util.AbstractCollection;
import java.util.Iterator;
public class IterableCollection<T> extends AbstractCollection<T> {
private final CompositeIterable<T> iterable;
public IterableCollection(Iterable<T> iterable) {
this(new CompositeIterable(iterable));
}
public IterableCollection(CompositeIterable<T> iterable) {
this.iterable = iterable;
}
@Override
public Iterator<T> iterator() {
return new CompositeIterable<T>(iterable).iterator();
}
@Override
public int size() {
Iterator<T> iterator = new CompositeIterable<T>(iterable).iterator();
int count = 0;
while (iterator.hasNext()) {
iterator.next();
count++;
}
return count;
}
}

39
fine-socketio/src/com/fr/third/socketio/namespace/EventEntry.java

@ -0,0 +1,39 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.namespace;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import com.fr.third.socketio.listener.DataListener;
public class EventEntry<T> {
private final Queue<DataListener<T>> listeners = new ConcurrentLinkedQueue<DataListener<T>>();;
public EventEntry() {
super();
}
public void addListener(DataListener<T> listener) {
listeners.add(listener);
}
public Queue<DataListener<T>> getListeners() {
return listeners;
}
}

381
fine-socketio/src/com/fr/third/socketio/namespace/Namespace.java

@ -0,0 +1,381 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.namespace;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
import com.fr.third.socketio.AckMode;
import com.fr.third.socketio.AckRequest;
import com.fr.third.socketio.BroadcastOperations;
import com.fr.third.socketio.Configuration;
import com.fr.third.socketio.MultiTypeArgs;
import com.fr.third.socketio.SocketIOClient;
import com.fr.third.socketio.SocketIONamespace;
import com.fr.third.socketio.annotation.ScannerEngine;
import com.fr.third.socketio.listener.*;
import com.fr.third.socketio.listener.ConnectListener;
import com.fr.third.socketio.listener.DataListener;
import com.fr.third.socketio.listener.DisconnectListener;
import com.fr.third.socketio.listener.ExceptionListener;
import com.fr.third.socketio.listener.MultiTypeEventListener;
import com.fr.third.socketio.listener.PingListener;
import com.fr.third.socketio.protocol.JsonSupport;
import com.fr.third.socketio.protocol.Packet;
import com.fr.third.socketio.store.StoreFactory;
import com.fr.third.socketio.store.pubsub.JoinLeaveMessage;
import com.fr.third.socketio.store.pubsub.PubSubType;
import com.fr.third.socketio.transport.NamespaceClient;
import io.netty.util.internal.PlatformDependent;
/**
* Hub object for all clients in one namespace.
* Namespace shares by different namespace-clients.
*
* @see NamespaceClient
*/
public class Namespace implements SocketIONamespace {
public static final String DEFAULT_NAME = "";
private final ScannerEngine engine = new ScannerEngine();
private final ConcurrentMap<String, EventEntry<?>> eventListeners = PlatformDependent.newConcurrentHashMap();
private final Queue<ConnectListener> connectListeners = new ConcurrentLinkedQueue<ConnectListener>();
private final Queue<DisconnectListener> disconnectListeners = new ConcurrentLinkedQueue<DisconnectListener>();
private final Queue<PingListener> pingListeners = new ConcurrentLinkedQueue<PingListener>();
private final Map<UUID, SocketIOClient> allClients = PlatformDependent.newConcurrentHashMap();
private final ConcurrentMap<String, Set<UUID>> roomClients = PlatformDependent.newConcurrentHashMap();
private final ConcurrentMap<UUID, Set<String>> clientRooms = PlatformDependent.newConcurrentHashMap();
private final String name;
private final AckMode ackMode;
private final JsonSupport jsonSupport;
private final StoreFactory storeFactory;
private final ExceptionListener exceptionListener;
public Namespace(String name, Configuration configuration) {
super();
this.name = name;
this.jsonSupport = configuration.getJsonSupport();
this.storeFactory = configuration.getStoreFactory();
this.exceptionListener = configuration.getExceptionListener();
this.ackMode = configuration.getAckMode();
}
public void addClient(SocketIOClient client) {
allClients.put(client.getSessionId(), client);
}
@Override
public String getName() {
return name;
}
@Override
public void addMultiTypeEventListener(String eventName, MultiTypeEventListener listener,
Class<?>... eventClass) {
EventEntry entry = eventListeners.get(eventName);
if (entry == null) {
entry = new EventEntry();
EventEntry<?> oldEntry = eventListeners.putIfAbsent(eventName, entry);
if (oldEntry != null) {
entry = oldEntry;
}
}
entry.addListener(listener);
jsonSupport.addEventMapping(name, eventName, eventClass);
}
@Override
public void removeAllListeners(String eventName) {
EventEntry<?> entry = eventListeners.remove(eventName);
if (entry != null) {
jsonSupport.removeEventMapping(name, eventName);
}
}
@Override
@SuppressWarnings({"unchecked", "rawtypes"})
public <T> void addEventListener(String eventName, Class<T> eventClass, DataListener<T> listener) {
EventEntry entry = eventListeners.get(eventName);
if (entry == null) {
entry = new EventEntry<T>();
EventEntry<?> oldEntry = eventListeners.putIfAbsent(eventName, entry);
if (oldEntry != null) {
entry = oldEntry;
}
}
entry.addListener(listener);
jsonSupport.addEventMapping(name, eventName, eventClass);
}
@SuppressWarnings({"rawtypes", "unchecked"})
public void onEvent(NamespaceClient client, String eventName, List<Object> args, AckRequest ackRequest) {
EventEntry entry = eventListeners.get(eventName);
if (entry == null) {
return;
}
try {
Queue<DataListener> listeners = entry.getListeners();
for (DataListener dataListener : listeners) {
Object data = getEventData(args, dataListener);
dataListener.onData(client, data, ackRequest);
}
} catch (Exception e) {
exceptionListener.onEventException(e, args, client);
if (ackMode == AckMode.AUTO_SUCCESS_ONLY) {
return;
}
}
sendAck(ackRequest);
}
private void sendAck(AckRequest ackRequest) {
if (ackMode == AckMode.AUTO || ackMode == AckMode.AUTO_SUCCESS_ONLY) {
// send ack response if it not executed
// during {@link DataListener#onData} invocation
ackRequest.sendAckData(Collections.emptyList());
}
}
private Object getEventData(List<Object> args, DataListener<?> dataListener) {
if (dataListener instanceof MultiTypeEventListener) {
return new MultiTypeArgs(args);
} else {
if (!args.isEmpty()) {
return args.get(0);
}
}
return null;
}
@Override
public void addDisconnectListener(DisconnectListener listener) {
disconnectListeners.add(listener);
}
public void onDisconnect(SocketIOClient client) {
Set<String> joinedRooms = client.getAllRooms();
allClients.remove(client.getSessionId());
leave(getName(), client.getSessionId());
storeFactory.pubSubStore().publish(PubSubType.LEAVE, new JoinLeaveMessage(client.getSessionId(), getName(), getName()));
for (String joinedRoom : joinedRooms) {
leave(roomClients, joinedRoom, client.getSessionId());
}
clientRooms.remove(client.getSessionId());
try {
for (DisconnectListener listener : disconnectListeners) {
listener.onDisconnect(client);
}
} catch (Exception e) {
exceptionListener.onDisconnectException(e, client);
}
}
@Override
public void addConnectListener(ConnectListener listener) {
connectListeners.add(listener);
}
public void onConnect(SocketIOClient client) {
join(getName(), client.getSessionId());
storeFactory.pubSubStore().publish(PubSubType.JOIN, new JoinLeaveMessage(client.getSessionId(), getName(), getName()));
try {
for (ConnectListener listener : connectListeners) {
listener.onConnect(client);
}
} catch (Exception e) {
exceptionListener.onConnectException(e, client);
}
}
@Override
public void addPingListener(PingListener listener) {
pingListeners.add(listener);
}
public void onPing(SocketIOClient client) {
try {
for (PingListener listener : pingListeners) {
listener.onPing(client);
}
} catch (Exception e) {
exceptionListener.onPingException(e, client);
}
}
@Override
public BroadcastOperations getBroadcastOperations() {
return new BroadcastOperations(allClients.values(), storeFactory);
}
@Override
public BroadcastOperations getRoomOperations(String room) {
return new BroadcastOperations(getRoomClients(room), storeFactory);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Namespace other = (Namespace) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
@Override
public void addListeners(Object listeners) {
addListeners(listeners, listeners.getClass());
}
@Override
public void addListeners(Object listeners, Class<?> listenersClass) {
engine.scan(this, listeners, listenersClass);
}
public void joinRoom(String room, UUID sessionId) {
join(room, sessionId);
storeFactory.pubSubStore().publish(PubSubType.JOIN, new JoinLeaveMessage(sessionId, room, getName()));
}
public void dispatch(String room, Packet packet) {
Iterable<SocketIOClient> clients = getRoomClients(room);
for (SocketIOClient socketIOClient : clients) {
socketIOClient.send(packet);
}
}
private <K, V> void join(ConcurrentMap<K, Set<V>> map, K key, V value) {
Set<V> clients = map.get(key);
if (clients == null) {
clients = Collections.newSetFromMap(PlatformDependent.<V, Boolean>newConcurrentHashMap());
Set<V> oldClients = map.putIfAbsent(key, clients);
if (oldClients != null) {
clients = oldClients;
}
}
clients.add(value);
// object may be changed due to other concurrent call
if (clients != map.get(key)) {
// re-join if queue has been replaced
join(map, key, value);
}
}
public void join(String room, UUID sessionId) {
join(roomClients, room, sessionId);
join(clientRooms, sessionId, room);
}
public void leaveRoom(String room, UUID sessionId) {
leave(room, sessionId);
storeFactory.pubSubStore().publish(PubSubType.LEAVE, new JoinLeaveMessage(sessionId, room, getName()));
}
private <K, V> void leave(ConcurrentMap<K, Set<V>> map, K room, V sessionId) {
Set<V> clients = map.get(room);
if (clients == null) {
return;
}
clients.remove(sessionId);
if (clients.isEmpty()) {
map.remove(room, Collections.emptySet());
}
}
public void leave(String room, UUID sessionId) {
leave(roomClients, room, sessionId);
leave(clientRooms, sessionId, room);
}
public Set<String> getRooms(SocketIOClient client) {
Set<String> res = clientRooms.get(client.getSessionId());
if (res == null) {
return Collections.emptySet();
}
return Collections.unmodifiableSet(res);
}
public Set<String> getRooms() {
return roomClients.keySet();
}
public Iterable<SocketIOClient> getRoomClients(String room) {
Set<UUID> sessionIds = roomClients.get(room);
if (sessionIds == null) {
return Collections.emptyList();
}
List<SocketIOClient> result = new ArrayList<SocketIOClient>();
for (UUID sessionId : sessionIds) {
SocketIOClient client = allClients.get(sessionId);
if(client != null) {
result.add(client);
}
}
return result;
}
@Override
public Collection<SocketIOClient> getAllClients() {
return Collections.unmodifiableCollection(allClients.values());
}
public JsonSupport getJsonSupport() {
return jsonSupport;
}
@Override
public SocketIOClient getClient(UUID uuid) {
return allClients.get(uuid);
}
}

75
fine-socketio/src/com/fr/third/socketio/namespace/NamespacesHub.java

@ -0,0 +1,75 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.namespace;
import com.fr.third.socketio.Configuration;
import com.fr.third.socketio.SocketIOClient;
import com.fr.third.socketio.SocketIONamespace;
import io.netty.util.internal.PlatformDependent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ConcurrentMap;
import com.fr.third.socketio.misc.CompositeIterable;
public class NamespacesHub {
private final ConcurrentMap<String, SocketIONamespace> namespaces = PlatformDependent.newConcurrentHashMap();
private final Configuration configuration;
public NamespacesHub(Configuration configuration) {
this.configuration = configuration;
}
public Namespace create(String name) {
Namespace namespace = (Namespace) namespaces.get(name);
if (namespace == null) {
namespace = new Namespace(name, configuration);
Namespace oldNamespace = (Namespace) namespaces.putIfAbsent(name, namespace);
if (oldNamespace != null) {
namespace = oldNamespace;
}
}
return namespace;
}
public Iterable<SocketIOClient> getRoomClients(String room) {
List<Iterable<SocketIOClient>> allClients = new ArrayList<Iterable<SocketIOClient>>();
for (SocketIONamespace namespace : namespaces.values()) {
Iterable<SocketIOClient> clients = ((Namespace)namespace).getRoomClients(room);
allClients.add(clients);
}
return new CompositeIterable<SocketIOClient>(allClients);
}
public Namespace get(String name) {
return (Namespace) namespaces.get(name);
}
public void remove(String name) {
SocketIONamespace namespace = namespaces.remove(name);
if (namespace != null) {
namespace.getBroadcastOperations().disconnect();
}
}
public Collection<SocketIONamespace> getAllNamespaces() {
return namespaces.values();
}
}

33
fine-socketio/src/com/fr/third/socketio/protocol/AckArgs.java

@ -0,0 +1,33 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.protocol;
import java.util.List;
public class AckArgs {
private List<Object> args;
public AckArgs(List<Object> args) {
super();
this.args = args;
}
public List<Object> getArgs() {
return args;
}
}

52
fine-socketio/src/com/fr/third/socketio/protocol/AuthPacket.java

@ -0,0 +1,52 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.protocol;
import java.util.UUID;
public class AuthPacket {
private final UUID sid;
private final String[] upgrades;
private final int pingInterval;
private final int pingTimeout;
public AuthPacket(UUID sid, String[] upgrades, int pingInterval, int pingTimeout) {
super();
this.sid = sid;
this.upgrades = upgrades;
this.pingInterval = pingInterval;
this.pingTimeout = pingTimeout;
}
public int getPingInterval() {
return pingInterval;
}
public int getPingTimeout() {
return pingTimeout;
}
public UUID getSid() {
return sid;
}
public String[] getUpgrades() {
return upgrades;
}
}

42
fine-socketio/src/com/fr/third/socketio/protocol/Event.java

@ -0,0 +1,42 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.protocol;
import java.util.List;
class Event {
private String name;
private List<Object> args;
public Event() {
}
public Event(String name, List<Object> args) {
super();
this.name = name;
this.args = args;
}
public List<Object> getArgs() {
return args;
}
public String getName() {
return name;
}
}

360
fine-socketio/src/com/fr/third/socketio/protocol/JacksonJsonSupport.java

@ -0,0 +1,360 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.protocol;
import com.fr.third.socketio.AckCallback;
import com.fr.third.socketio.MultiTypeAckCallback;
import com.fr.third.socketio.namespace.Namespace;
import io.netty.buffer.ByteBufInputStream;
import io.netty.buffer.ByteBufOutputStream;
import io.netty.util.internal.PlatformDependent;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fr.third.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fr.third.fasterxml.jackson.core.JsonGenerationException;
import com.fr.third.fasterxml.jackson.core.JsonGenerator;
import com.fr.third.fasterxml.jackson.core.JsonParser;
import com.fr.third.fasterxml.jackson.core.JsonProcessingException;
import com.fr.third.fasterxml.jackson.core.JsonToken;
import com.fr.third.fasterxml.jackson.databind.BeanDescription;
import com.fr.third.fasterxml.jackson.databind.DeserializationContext;
import com.fr.third.fasterxml.jackson.databind.DeserializationFeature;
import com.fr.third.fasterxml.jackson.databind.JavaType;
import com.fr.third.fasterxml.jackson.databind.JsonMappingException;
import com.fr.third.fasterxml.jackson.databind.JsonNode;
import com.fr.third.fasterxml.jackson.databind.JsonSerializer;
import com.fr.third.fasterxml.jackson.databind.Module;
import com.fr.third.fasterxml.jackson.databind.ObjectMapper;
import com.fr.third.fasterxml.jackson.databind.SerializationConfig;
import com.fr.third.fasterxml.jackson.databind.SerializationFeature;
import com.fr.third.fasterxml.jackson.databind.SerializerProvider;
import com.fr.third.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fr.third.fasterxml.jackson.databind.jsonFormatVisitors.JsonArrayFormatVisitor;
import com.fr.third.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatTypes;
import com.fr.third.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
import com.fr.third.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fr.third.fasterxml.jackson.databind.module.SimpleModule;
import com.fr.third.fasterxml.jackson.databind.node.ObjectNode;
import com.fr.third.fasterxml.jackson.databind.ser.BeanSerializerModifier;
import com.fr.third.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.fr.third.fasterxml.jackson.databind.type.ArrayType;
public class JacksonJsonSupport implements JsonSupport {
private class AckArgsDeserializer extends StdDeserializer<AckArgs> {
private static final long serialVersionUID = 7810461017389946707L;
protected AckArgsDeserializer() {
super(AckArgs.class);
}
@Override
public AckArgs deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException,
JsonProcessingException {
List<Object> args = new ArrayList<Object>();
AckArgs result = new AckArgs(args);
ObjectMapper mapper = (ObjectMapper) jp.getCodec();
JsonNode root = mapper.readTree(jp);
AckCallback<?> callback = currentAckClass.get();
Iterator<JsonNode> iter = root.iterator();
int i = 0;
while (iter.hasNext()) {
Object val;
Class<?> clazz = callback.getResultClass();
if (callback instanceof MultiTypeAckCallback) {
MultiTypeAckCallback multiTypeAckCallback = (MultiTypeAckCallback) callback;
clazz = multiTypeAckCallback.getResultClasses()[i];
}
JsonNode arg = iter.next();
if (arg.isTextual() || arg.isBoolean()) {
clazz = Object.class;
}
val = mapper.treeToValue(arg, clazz);
args.add(val);
i++;
}
return result;
}
}
public static class EventKey {
private String namespaceName;
private String eventName;
public EventKey(String namespaceName, String eventName) {
super();
this.namespaceName = namespaceName;
this.eventName = eventName;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((eventName == null) ? 0 : eventName.hashCode());
result = prime * result + ((namespaceName == null) ? 0 : namespaceName.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
EventKey other = (EventKey) obj;
if (eventName == null) {
if (other.eventName != null)
return false;
} else if (!eventName.equals(other.eventName))
return false;
if (namespaceName == null) {
if (other.namespaceName != null)
return false;
} else if (!namespaceName.equals(other.namespaceName))
return false;
return true;
}
}
private class EventDeserializer extends StdDeserializer<Event> {
private static final long serialVersionUID = 8178797221017768689L;
final Map<EventKey, List<Class<?>>> eventMapping = PlatformDependent.newConcurrentHashMap();
protected EventDeserializer() {
super(Event.class);
}
@Override
public Event deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException,
JsonProcessingException {
ObjectMapper mapper = (ObjectMapper) jp.getCodec();
String eventName = jp.nextTextValue();
EventKey ek = new EventKey(namespaceClass.get(), eventName);
if (!eventMapping.containsKey(ek)) {
ek = new EventKey(Namespace.DEFAULT_NAME, eventName);
if (!eventMapping.containsKey(ek)) {
return new Event(eventName, Collections.emptyList());
}
}
List<Object> eventArgs = new ArrayList<Object>();
Event event = new Event(eventName, eventArgs);
List<Class<?>> eventClasses = eventMapping.get(ek);
int i = 0;
while (true) {
JsonToken token = jp.nextToken();
if (token == JsonToken.END_ARRAY) {
break;
}
if (i > eventClasses.size() - 1) {
log.debug("Event {} has more args than declared in handler: {}", eventName, null);
break;
}
Class<?> eventClass = eventClasses.get(i);
Object arg = mapper.readValue(jp, eventClass);
eventArgs.add(arg);
i++;
}
return event;
}
}
public static class ByteArraySerializer extends StdSerializer<byte[]>
{
private static final long serialVersionUID = 3420082888596468148L;
private final ThreadLocal<List<byte[]>> arrays = new ThreadLocal<List<byte[]>>() {
@Override
protected List<byte[]> initialValue() {
return new ArrayList<byte[]>();
};
};
public ByteArraySerializer() {
super(byte[].class);
}
@Override
public boolean isEmpty(byte[] value) {
return (value == null) || (value.length == 0);
}
@Override
public void serialize(byte[] value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonGenerationException
{
Map<String, Object> map = new HashMap<String, Object>();
map.put("num", arrays.get().size());
map.put("_placeholder", true);
jgen.writeObject(map);
arrays.get().add(value);
}
@Override
public void serializeWithType(byte[] value, JsonGenerator jgen, SerializerProvider provider,
TypeSerializer typeSer)
throws IOException, JsonGenerationException
{
serialize(value, jgen, provider);
}
@Override
public JsonNode getSchema(SerializerProvider provider, Type typeHint)
{
ObjectNode o = createSchemaNode("array", true);
ObjectNode itemSchema = createSchemaNode("string"); //binary values written as strings?
return o.set("items", itemSchema);
}
@Override
public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint)
throws JsonMappingException
{
if (visitor != null) {
JsonArrayFormatVisitor v2 = visitor.expectArrayFormat(typeHint);
if (v2 != null) {
v2.itemsFormat(JsonFormatTypes.STRING);
}
}
}
public List<byte[]> getArrays() {
return arrays.get();
}
public void clear() {
arrays.set(new ArrayList<byte[]>());
}
}
private class ExBeanSerializerModifier extends BeanSerializerModifier {
private final ByteArraySerializer serializer = new ByteArraySerializer();
@Override
public JsonSerializer<?> modifyArraySerializer(SerializationConfig config, ArrayType valueType,
BeanDescription beanDesc, JsonSerializer<?> serializer) {
if (valueType.getRawClass().equals(byte[].class)) {
return this.serializer;
}
return super.modifyArraySerializer(config, valueType, beanDesc, serializer);
}
public ByteArraySerializer getSerializer() {
return serializer;
}
}
protected final ExBeanSerializerModifier modifier = new ExBeanSerializerModifier();
protected final ThreadLocal<String> namespaceClass = new ThreadLocal<String>();
protected final ThreadLocal<AckCallback<?>> currentAckClass = new ThreadLocal<AckCallback<?>>();
protected final ObjectMapper objectMapper = new ObjectMapper();
protected final EventDeserializer eventDeserializer = new EventDeserializer();
protected final AckArgsDeserializer ackArgsDeserializer = new AckArgsDeserializer();
protected static final Logger log = LoggerFactory.getLogger(JacksonJsonSupport.class);
public JacksonJsonSupport() {
this(new Module[] {});
}
public JacksonJsonSupport(Module... modules) {
if (modules != null && modules.length > 0) {
objectMapper.registerModules(modules);
}
init(objectMapper);
}
protected void init(ObjectMapper objectMapper) {
SimpleModule module = new SimpleModule();
module.setSerializerModifier(modifier);
module.addDeserializer(Event.class, eventDeserializer);
module.addDeserializer(AckArgs.class, ackArgsDeserializer);
objectMapper.registerModule(module);
objectMapper.setSerializationInclusion(Include.NON_NULL);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure(SerializationFeature.WRITE_BIGDECIMAL_AS_PLAIN, true);
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
}
@Override
public void addEventMapping(String namespaceName, String eventName, Class<?> ... eventClass) {
eventDeserializer.eventMapping.put(new EventKey(namespaceName, eventName), Arrays.asList(eventClass));
}
@Override
public void removeEventMapping(String namespaceName, String eventName) {
eventDeserializer.eventMapping.remove(new EventKey(namespaceName, eventName));
}
@Override
public <T> T readValue(String namespaceName, ByteBufInputStream src, Class<T> valueType) throws IOException {
namespaceClass.set(namespaceName);
return objectMapper.readValue(src, valueType);
}
@Override
public AckArgs readAckArgs(ByteBufInputStream src, AckCallback<?> callback) throws IOException {
currentAckClass.set(callback);
return objectMapper.readValue(src, AckArgs.class);
}
@Override
public void writeValue(ByteBufOutputStream out, Object value) throws IOException {
modifier.getSerializer().clear();
objectMapper.writeValue(out, value);
}
@Override
public List<byte[]> getArrays() {
return modifier.getSerializer().getArrays();
}
}

45
fine-socketio/src/com/fr/third/socketio/protocol/JsonSupport.java

@ -0,0 +1,45 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.protocol;
import com.fr.third.socketio.AckCallback;
import io.netty.buffer.ByteBufInputStream;
import io.netty.buffer.ByteBufOutputStream;
import java.io.IOException;
import java.util.List;
/**
* JSON infrastructure interface.
* Allows to implement custom realizations
* to JSON support operations.
*
*/
public interface JsonSupport {
AckArgs readAckArgs(ByteBufInputStream src, AckCallback<?> callback) throws IOException;
<T> T readValue(String namespaceName, ByteBufInputStream src, Class<T> valueType) throws IOException;
void writeValue(ByteBufOutputStream out, Object value) throws IOException;
void addEventMapping(String namespaceName, String eventName, Class<?> ... eventClass);
void removeEventMapping(String namespaceName, String eventName);
List<byte[]> getArrays();
}

138
fine-socketio/src/com/fr/third/socketio/protocol/Packet.java

@ -0,0 +1,138 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.protocol;
import com.fr.third.socketio.namespace.Namespace;
import io.netty.buffer.ByteBuf;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Packet implements Serializable {
private static final long serialVersionUID = 4560159536486711426L;
private PacketType type;
private PacketType subType;
private Long ackId;
private String name;
private String nsp = Namespace.DEFAULT_NAME;
private Object data;
private ByteBuf dataSource;
private int attachmentsCount;
private List<ByteBuf> attachments = Collections.emptyList();
protected Packet() {
}
public Packet(PacketType type) {
super();
this.type = type;
}
public PacketType getSubType() {
return subType;
}
public void setSubType(PacketType subType) {
this.subType = subType;
}
public PacketType getType() {
return type;
}
public void setData(Object data) {
this.data = data;
}
/**
* Get packet data
*
* @param <T> the type data
*
* <pre>
* @return <b>json object</b> for PacketType.JSON type
* <b>message</b> for PacketType.MESSAGE type
* </pre>
*/
public <T> T getData() {
return (T)data;
}
public void setNsp(String endpoint) {
this.nsp = endpoint;
}
public String getNsp() {
return nsp;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Long getAckId() {
return ackId;
}
public void setAckId(Long ackId) {
this.ackId = ackId;
}
public boolean isAckRequested() {
return getAckId() != null;
}
public void initAttachments(int attachmentsCount) {
this.attachmentsCount = attachmentsCount;
this.attachments = new ArrayList<ByteBuf>(attachmentsCount);
}
public void addAttachment(ByteBuf attachment) {
if (this.attachments.size() < attachmentsCount) {
this.attachments.add(attachment);
}
}
public List<ByteBuf> getAttachments() {
return attachments;
}
public boolean hasAttachments() {
return attachmentsCount != 0;
}
public boolean isAttachmentsLoaded() {
return this.attachments.size() == attachmentsCount;
}
public ByteBuf getDataSource() {
return dataSource;
}
public void setDataSource(ByteBuf dataSource) {
this.dataSource = dataSource;
}
@Override
public String toString() {
return "Packet [type=" + type + ", ackId=" + ackId + "]";
}
}

313
fine-socketio/src/com/fr/third/socketio/protocol/PacketDecoder.java

@ -0,0 +1,313 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.protocol;
import com.fr.third.socketio.AckCallback;
import com.fr.third.socketio.ack.AckManager;
import com.fr.third.socketio.handler.ClientHead;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufInputStream;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.base64.Base64;
import io.netty.util.CharsetUtil;
import java.io.IOException;
import java.net.URLDecoder;
import java.util.LinkedList;
import java.util.UUID;
public class PacketDecoder {
private final UTF8CharsScanner utf8scanner = new UTF8CharsScanner();
private final ByteBuf QUOTES = Unpooled.copiedBuffer("\"", CharsetUtil.UTF_8);
private final JsonSupport jsonSupport;
private final AckManager ackManager;
public PacketDecoder(JsonSupport jsonSupport, AckManager ackManager) {
this.jsonSupport = jsonSupport;
this.ackManager = ackManager;
}
private boolean isStringPacket(ByteBuf content) {
return content.getByte(content.readerIndex()) == 0x0;
}
// TODO optimize
public ByteBuf preprocessJson(Integer jsonIndex, ByteBuf content) throws IOException {
String packet = URLDecoder.decode(content.toString(CharsetUtil.UTF_8), CharsetUtil.UTF_8.name());
if (jsonIndex != null) {
/**
* double escaping is required for escaped new lines because unescaping of new lines can be done safely on server-side
* (c) socket.io.js
*
* @see https://github.com/Automattic/socket.io-client/blob/1.3.3/socket.io.js#L2682
*/
packet = packet.replace("\\\\n", "\\n");
// skip "d="
packet = packet.substring(2);
}
return Unpooled.wrappedBuffer(packet.getBytes(CharsetUtil.UTF_8));
}
// fastest way to parse chars to int
private long readLong(ByteBuf chars, int length) {
long result = 0;
for (int i = chars.readerIndex(); i < chars.readerIndex() + length; i++) {
int digit = ((int)chars.getByte(i) & 0xF);
for (int j = 0; j < chars.readerIndex() + length-1-i; j++) {
digit *= 10;
}
result += digit;
}
chars.readerIndex(chars.readerIndex() + length);
return result;
}
private PacketType readType(ByteBuf buffer) {
int typeId = buffer.readByte() & 0xF;
return PacketType.valueOf(typeId);
}
private PacketType readInnerType(ByteBuf buffer) {
int typeId = buffer.readByte() & 0xF;
return PacketType.valueOfInner(typeId);
}
@Deprecated
public Packet decodePacket(String string, UUID uuid) throws IOException {
ByteBuf buf = Unpooled.copiedBuffer(string, CharsetUtil.UTF_8);
try {
return null;
} finally {
buf.release();
}
}
private boolean hasLengthHeader(ByteBuf buffer) {
for (int i = 0; i < Math.min(buffer.readableBytes(), 10); i++) {
byte b = buffer.getByte(buffer.readerIndex() + i);
if (b == (byte)':' && i > 0) {
return true;
}
if (b > 57 || b < 48) {
return false;
}
}
return false;
}
public Packet decodePackets(ByteBuf buffer, ClientHead client) throws IOException {
if (isStringPacket(buffer)) {
// TODO refactor
int maxLength = Math.min(buffer.readableBytes(), 10);
int headEndIndex = buffer.bytesBefore(maxLength, (byte)-1);
if (headEndIndex == -1) {
headEndIndex = buffer.bytesBefore(maxLength, (byte)0x3f);
}
int len = (int) readLong(buffer, headEndIndex);
ByteBuf frame = buffer.slice(buffer.readerIndex() + 1, len);
// skip this frame
buffer.readerIndex(buffer.readerIndex() + 1 + len);
return decode(client, frame);
} else if (hasLengthHeader(buffer)) {
// TODO refactor
int lengthEndIndex = buffer.bytesBefore((byte)':');
int lenHeader = (int) readLong(buffer, lengthEndIndex);
int len = utf8scanner.getActualLength(buffer, lenHeader);
ByteBuf frame = buffer.slice(buffer.readerIndex() + 1, len);
// skip this frame
buffer.readerIndex(buffer.readerIndex() + 1 + len);
return decode(client, frame);
}
return decode(client, buffer);
}
private String readString(ByteBuf frame) {
return readString(frame, frame.readableBytes());
}
private String readString(ByteBuf frame, int size) {
byte[] bytes = new byte[size];
frame.readBytes(bytes);
return new String(bytes, CharsetUtil.UTF_8);
}
private Packet decode(ClientHead head, ByteBuf frame) throws IOException {
if ((frame.getByte(0) == 'b' && frame.getByte(1) == '4')
|| frame.getByte(0) == 4 || frame.getByte(0) == 1) {
return parseBinary(head, frame);
}
PacketType type = readType(frame);
Packet packet = new Packet(type);
if (type == PacketType.PING) {
packet.setData(readString(frame));
return packet;
}
if (!frame.isReadable()) {
return packet;
}
PacketType innerType = readInnerType(frame);
packet.setSubType(innerType);
parseHeader(frame, packet, innerType);
parseBody(head, frame, packet);
return packet;
}
private void parseHeader(ByteBuf frame, Packet packet, PacketType innerType) {
int endIndex = frame.bytesBefore((byte)'[');
if (endIndex <= 0) {
return;
}
int attachmentsDividerIndex = frame.bytesBefore(endIndex, (byte)'-');
boolean hasAttachments = attachmentsDividerIndex != -1;
if (hasAttachments && (PacketType.BINARY_EVENT.equals(innerType)
|| PacketType.BINARY_ACK.equals(innerType))) {
int attachments = (int) readLong(frame, attachmentsDividerIndex);
packet.initAttachments(attachments);
frame.readerIndex(frame.readerIndex() + 1);
endIndex -= attachmentsDividerIndex + 1;
}
if (endIndex == 0) {
return;
}
// TODO optimize
boolean hasNsp = frame.bytesBefore(endIndex, (byte)',') != -1;
if (hasNsp) {
String nspAckId = readString(frame, endIndex);
String[] parts = nspAckId.split(",");
String nsp = parts[0];
packet.setNsp(nsp);
if (parts.length > 1) {
String ackId = parts[1];
packet.setAckId(Long.valueOf(ackId));
}
} else {
long ackId = readLong(frame, endIndex);
packet.setAckId(ackId);
}
}
private Packet parseBinary(ClientHead head, ByteBuf frame) throws IOException {
if (frame.getByte(0) == 1) {
frame.readByte();
int headEndIndex = frame.bytesBefore((byte)-1);
int len = (int) readLong(frame, headEndIndex);
ByteBuf oldFrame = frame;
frame = frame.slice(oldFrame.readerIndex() + 1, len);
oldFrame.readerIndex(oldFrame.readerIndex() + 1 + len);
}
if (frame.getByte(0) == 'b' && frame.getByte(1) == '4') {
frame.readShort();
} else if (frame.getByte(0) == 4) {
frame.readByte();
}
Packet binaryPacket = head.getLastBinaryPacket();
if (binaryPacket != null) {
if (frame.getByte(0) == 'b' && frame.getByte(1) == '4') {
binaryPacket.addAttachment(Unpooled.copiedBuffer(frame));
} else {
ByteBuf attachBuf = Base64.encode(frame);
binaryPacket.addAttachment(Unpooled.copiedBuffer(attachBuf));
attachBuf.release();
}
frame.readerIndex(frame.readerIndex() + frame.readableBytes());
if (binaryPacket.isAttachmentsLoaded()) {
LinkedList<ByteBuf> slices = new LinkedList<ByteBuf>();
ByteBuf source = binaryPacket.getDataSource();
for (int i = 0; i < binaryPacket.getAttachments().size(); i++) {
ByteBuf attachment = binaryPacket.getAttachments().get(i);
ByteBuf scanValue = Unpooled.copiedBuffer("{\"_placeholder\":true,\"num\":" + i + "}", CharsetUtil.UTF_8);
int pos = PacketEncoder.find(source, scanValue);
if (pos == -1) {
scanValue = Unpooled.copiedBuffer("{\"num\":" + i + ",\"_placeholder\":true}", CharsetUtil.UTF_8);
pos = PacketEncoder.find(source, scanValue);
if (pos == -1) {
throw new IllegalStateException("Can't find attachment by index: " + i + " in packet source");
}
}
ByteBuf prefixBuf = source.slice(source.readerIndex(), pos - source.readerIndex());
slices.add(prefixBuf);
slices.add(QUOTES);
slices.add(attachment);
slices.add(QUOTES);
source.readerIndex(pos + scanValue.readableBytes());
}
slices.add(source.slice());
ByteBuf compositeBuf = Unpooled.wrappedBuffer(slices.toArray(new ByteBuf[slices.size()]));
parseBody(head, compositeBuf, binaryPacket);
head.setLastBinaryPacket(null);
return binaryPacket;
}
}
return new Packet(PacketType.MESSAGE);
}
private void parseBody(ClientHead head, ByteBuf frame, Packet packet) throws IOException {
if (packet.getType() == PacketType.MESSAGE) {
if (packet.getSubType() == PacketType.CONNECT
|| packet.getSubType() == PacketType.DISCONNECT) {
packet.setNsp(readString(frame));
}
if (packet.hasAttachments() && !packet.isAttachmentsLoaded()) {
packet.setDataSource(Unpooled.copiedBuffer(frame));
frame.readerIndex(frame.readableBytes());
head.setLastBinaryPacket(packet);
}
if (packet.hasAttachments() && !packet.isAttachmentsLoaded()) {
return;
}
if (packet.getSubType() == PacketType.ACK
|| packet.getSubType() == PacketType.BINARY_ACK) {
ByteBufInputStream in = new ByteBufInputStream(frame);
AckCallback<?> callback = ackManager.getCallback(head.getSessionId(), packet.getAckId());
AckArgs args = jsonSupport.readAckArgs(in, callback);
packet.setData(args.getArgs());
}
if (packet.getSubType() == PacketType.EVENT
|| packet.getSubType() == PacketType.BINARY_EVENT) {
ByteBufInputStream in = new ByteBufInputStream(frame);
Event event = jsonSupport.readValue(packet.getNsp(), in, Event.class);
packet.setName(event.getName());
packet.setData(event.getArgs());
}
}
}
}

352
fine-socketio/src/com/fr/third/socketio/protocol/PacketEncoder.java

@ -0,0 +1,352 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.protocol;
import com.fr.third.socketio.Configuration;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.ByteBufOutputStream;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.base64.Base64;
import io.netty.handler.codec.base64.Base64Dialect;
import io.netty.util.CharsetUtil;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
public class PacketEncoder {
private static final byte[] BINARY_HEADER = "b4".getBytes(CharsetUtil.UTF_8);
private static final byte[] B64_DELIMITER = new byte[] {':'};
private static final byte[] JSONP_HEAD = "___eio[".getBytes(CharsetUtil.UTF_8);
private static final byte[] JSONP_START = "]('".getBytes(CharsetUtil.UTF_8);
private static final byte[] JSONP_END = "');".getBytes(CharsetUtil.UTF_8);
private final JsonSupport jsonSupport;
private final Configuration configuration;
public PacketEncoder(Configuration configuration, JsonSupport jsonSupport) {
this.jsonSupport = jsonSupport;
this.configuration = configuration;
}
public JsonSupport getJsonSupport() {
return jsonSupport;
}
public ByteBuf allocateBuffer(ByteBufAllocator allocator) {
if (configuration.isPreferDirectBuffer()) {
return allocator.ioBuffer();
}
return allocator.heapBuffer();
}
public void encodeJsonP(Integer jsonpIndex, Queue<Packet> packets, ByteBuf out, ByteBufAllocator allocator, int limit) throws IOException {
boolean jsonpMode = jsonpIndex != null;
ByteBuf buf = allocateBuffer(allocator);
int i = 0;
while (true) {
Packet packet = packets.poll();
if (packet == null || i == limit) {
break;
}
ByteBuf packetBuf = allocateBuffer(allocator);
encodePacket(packet, packetBuf, allocator, true);
int packetSize = packetBuf.writerIndex();
buf.writeBytes(toChars(packetSize));
buf.writeBytes(B64_DELIMITER);
buf.writeBytes(packetBuf);
packetBuf.release();
i++;
for (ByteBuf attachment : packet.getAttachments()) {
ByteBuf encodedBuf = Base64.encode(attachment, Base64Dialect.URL_SAFE);
buf.writeBytes(toChars(encodedBuf.readableBytes() + 2));
buf.writeBytes(B64_DELIMITER);
buf.writeBytes(BINARY_HEADER);
buf.writeBytes(encodedBuf);
}
}
if (jsonpMode) {
out.writeBytes(JSONP_HEAD);
out.writeBytes(toChars(jsonpIndex));
out.writeBytes(JSONP_START);
}
processUtf8(buf, out, jsonpMode);
buf.release();
if (jsonpMode) {
out.writeBytes(JSONP_END);
}
}
private void processUtf8(ByteBuf in, ByteBuf out, boolean jsonpMode) {
while (in.isReadable()) {
short value = (short) (in.readByte() & 0xFF);
if (value >>> 7 == 0) {
if (jsonpMode && (value == '\\' || value == '\'')) {
out.writeByte('\\');
}
out.writeByte(value);
} else {
out.writeByte(((value >>> 6) | 0xC0));
out.writeByte(((value & 0x3F) | 0x80));
}
}
}
public void encodePackets(Queue<Packet> packets, ByteBuf buffer, ByteBufAllocator allocator, int limit) throws IOException {
int i = 0;
while (true) {
Packet packet = packets.poll();
if (packet == null || i == limit) {
break;
}
encodePacket(packet, buffer, allocator, false);
i++;
for (ByteBuf attachment : packet.getAttachments()) {
buffer.writeByte(1);
buffer.writeBytes(longToBytes(attachment.readableBytes() + 1));
buffer.writeByte(0xff);
buffer.writeByte(4);
buffer.writeBytes(attachment);
}
}
}
private byte toChar(int number) {
return (byte) (number ^ 0x30);
}
static final char[] DigitTens = {'0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '1', '1', '1', '1',
'1', '1', '1', '1', '1', '1', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '3', '3', '3',
'3', '3', '3', '3', '3', '3', '3', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '5', '5',
'5', '5', '5', '5', '5', '5', '5', '5', '6', '6', '6', '6', '6', '6', '6', '6', '6', '6', '7',
'7', '7', '7', '7', '7', '7', '7', '7', '7', '8', '8', '8', '8', '8', '8', '8', '8', '8', '8',
'9', '9', '9', '9', '9', '9', '9', '9', '9', '9',};
static final char[] DigitOnes = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3',
'4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2',
'3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1',
'2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',};
static final char[] digits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e',
'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
'y', 'z'};
static final int[] sizeTable = {9, 99, 999, 9999, 99999, 999999, 9999999, 99999999, 999999999,
Integer.MAX_VALUE};
// Requires positive x
static int stringSize(long x) {
for (int i = 0;; i++)
if (x <= sizeTable[i])
return i + 1;
}
static void getChars(long i, int index, byte[] buf) {
long q, r;
int charPos = index;
byte sign = 0;
if (i < 0) {
sign = '-';
i = -i;
}
// Generate two digits per iteration
while (i >= 65536) {
q = i / 100;
// really: r = i - (q * 100);
r = i - ((q << 6) + (q << 5) + (q << 2));
i = q;
buf[--charPos] = (byte) DigitOnes[(int)r];
buf[--charPos] = (byte) DigitTens[(int)r];
}
// Fall thru to fast mode for smaller numbers
// assert(i <= 65536, i);
for (;;) {
q = (i * 52429) >>> (16 + 3);
r = i - ((q << 3) + (q << 1)); // r = i-(q*10) ...
buf[--charPos] = (byte) digits[(int)r];
i = q;
if (i == 0)
break;
}
if (sign != 0) {
buf[--charPos] = sign;
}
}
public static byte[] toChars(long i) {
int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
byte[] buf = new byte[size];
getChars(i, size, buf);
return buf;
}
public static byte[] longToBytes(long number) {
// TODO optimize
int length = (int)(Math.log10(number)+1);
byte[] res = new byte[length];
int i = length;
while (number > 0) {
res[--i] = (byte) (number % 10);
number = number / 10;
}
return res;
}
public void encodePacket(Packet packet, ByteBuf buffer, ByteBufAllocator allocator, boolean binary) throws IOException {
ByteBuf buf = buffer;
if (!binary) {
buf = allocateBuffer(allocator);
}
byte type = toChar(packet.getType().getValue());
buf.writeByte(type);
try {
switch (packet.getType()) {
case PONG: {
buf.writeBytes(packet.getData().toString().getBytes(CharsetUtil.UTF_8));
break;
}
case OPEN: {
ByteBufOutputStream out = new ByteBufOutputStream(buf);
jsonSupport.writeValue(out, packet.getData());
break;
}
case MESSAGE: {
ByteBuf encBuf = null;
if (packet.getSubType() == PacketType.ERROR) {
encBuf = allocateBuffer(allocator);
ByteBufOutputStream out = new ByteBufOutputStream(encBuf);
jsonSupport.writeValue(out, packet.getData());
}
if (packet.getSubType() == PacketType.EVENT
|| packet.getSubType() == PacketType.ACK) {
List<Object> values = new ArrayList<Object>();
if (packet.getSubType() == PacketType.EVENT) {
values.add(packet.getName());
}
encBuf = allocateBuffer(allocator);
List<Object> args = packet.getData();
values.addAll(args);
ByteBufOutputStream out = new ByteBufOutputStream(encBuf);
jsonSupport.writeValue(out, values);
if (!jsonSupport.getArrays().isEmpty()) {
packet.initAttachments(jsonSupport.getArrays().size());
for (byte[] array : jsonSupport.getArrays()) {
packet.addAttachment(Unpooled.wrappedBuffer(array));
}
packet.setSubType(packet.getSubType() == PacketType.ACK
? PacketType.BINARY_ACK : PacketType.BINARY_EVENT);
}
}
byte subType = toChar(packet.getSubType().getValue());
buf.writeByte(subType);
if (packet.hasAttachments()) {
byte[] ackId = toChars(packet.getAttachments().size());
buf.writeBytes(ackId);
buf.writeByte('-');
}
if (packet.getSubType() == PacketType.CONNECT) {
if (!packet.getNsp().isEmpty()) {
buf.writeBytes(packet.getNsp().getBytes(CharsetUtil.UTF_8));
}
} else {
if (!packet.getNsp().isEmpty()) {
buf.writeBytes(packet.getNsp().getBytes(CharsetUtil.UTF_8));
buf.writeByte(',');
}
}
if (packet.getAckId() != null) {
byte[] ackId = toChars(packet.getAckId());
buf.writeBytes(ackId);
}
if (encBuf != null) {
buf.writeBytes(encBuf);
encBuf.release();
}
break;
}
}
} finally {
// we need to write a buffer in any case
if (!binary) {
buffer.writeByte(0);
int length = buf.writerIndex();
buffer.writeBytes(longToBytes(length));
buffer.writeByte(0xff);
buffer.writeBytes(buf);
buf.release();
}
}
}
public static int find(ByteBuf buffer, ByteBuf searchValue) {
for (int i = buffer.readerIndex(); i < buffer.readerIndex() + buffer.readableBytes(); i++) {
if (isValueFound(buffer, i, searchValue)) {
return i;
}
}
return -1;
}
private static boolean isValueFound(ByteBuf buffer, int index, ByteBuf search) {
for (int i = 0; i < search.readableBytes(); i++) {
if (buffer.getByte(index + i) != search.getByte(i)) {
return false;
}
}
return true;
}
}

60
fine-socketio/src/com/fr/third/socketio/protocol/PacketType.java

@ -0,0 +1,60 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.protocol;
public enum PacketType {
OPEN(0), CLOSE(1), PING(2), PONG(3), MESSAGE(4), UPGRADE(5), NOOP(6),
CONNECT(0, true), DISCONNECT(1, true), EVENT(2, true), ACK(3, true), ERROR(4, true), BINARY_EVENT(5, true), BINARY_ACK(6, true);
public static final PacketType[] VALUES = values();
private final int value;
private final boolean inner;
PacketType(int value) {
this(value, false);
}
PacketType(int value, boolean inner) {
this.value = value;
this.inner = inner;
}
public int getValue() {
return value;
}
public static PacketType valueOf(int value) {
for (PacketType type : VALUES) {
if (type.getValue() == value && !type.inner) {
return type;
}
}
throw new IllegalStateException();
}
public static PacketType valueOfInner(int value) {
for (PacketType type : VALUES) {
if (type.getValue() == value && type.inner) {
return type;
}
}
throw new IllegalArgumentException("Can't parse " + value);
}
}

126
fine-socketio/src/com/fr/third/socketio/protocol/UTF8CharsScanner.java

@ -0,0 +1,126 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.protocol;
import io.netty.buffer.ByteBuf;
public class UTF8CharsScanner {
/**
* Lookup table used for determining which input characters need special
* handling when contained in text segment.
*/
static final int[] sInputCodes;
static {
/*
* 96 would do for most cases (backslash is ascii 94) but if we want to
* do lookups by raw bytes it's better to have full table
*/
int[] table = new int[256];
// Control chars and non-space white space are not allowed unquoted
for (int i = 0; i < 32; ++i) {
table[i] = -1;
}
// And then string end and quote markers are special too
table['"'] = 1;
table['\\'] = 1;
sInputCodes = table;
}
/**
* Additionally we can combine UTF-8 decoding info into similar data table.
*/
static final int[] sInputCodesUtf8;
static {
int[] table = new int[sInputCodes.length];
System.arraycopy(sInputCodes, 0, table, 0, sInputCodes.length);
for (int c = 128; c < 256; ++c) {
int code;
// We'll add number of bytes needed for decoding
if ((c & 0xE0) == 0xC0) { // 2 bytes (0x0080 - 0x07FF)
code = 2;
} else if ((c & 0xF0) == 0xE0) { // 3 bytes (0x0800 - 0xFFFF)
code = 3;
} else if ((c & 0xF8) == 0xF0) {
// 4 bytes; double-char with surrogates and all...
code = 4;
} else {
// And -1 seems like a good "universal" error marker...
code = -1;
}
table[c] = code;
}
sInputCodesUtf8 = table;
}
private int getCharTailIndex(ByteBuf inputBuffer, int i) {
int c = (int) inputBuffer.getByte(i) & 0xFF;
switch (sInputCodesUtf8[c]) {
case 2: // 2-byte UTF
i += 2;
break;
case 3: // 3-byte UTF
i += 3;
break;
case 4: // 4-byte UTF
i += 4;
break;
default:
i++;
break;
}
return i;
}
public int getLength(ByteBuf inputBuffer, int start) {
int len = 0;
for (int i = start; i < inputBuffer.writerIndex();) {
i = getCharTailIndex(inputBuffer, i);
len++;
}
return len;
}
public int getActualLength(ByteBuf inputBuffer, int length) {
int len = 0;
int start = inputBuffer.readerIndex();
for (int i = inputBuffer.readerIndex(); i < inputBuffer.readableBytes() + inputBuffer.readerIndex();) {
i = getCharTailIndex(inputBuffer, i);
len++;
if (length == len) {
return i-start;
}
}
throw new IllegalStateException();
}
public int findTailIndex(ByteBuf inputBuffer, int start, int end,
int charsToRead) {
int len = 0;
int i = start;
while (i < end) {
i = getCharTailIndex(inputBuffer, i);
len++;
if (charsToRead == len) {
break;
}
}
return i;
}
}

36
fine-socketio/src/com/fr/third/socketio/scheduler/CancelableScheduler.java

@ -0,0 +1,36 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.scheduler;
import io.netty.channel.ChannelHandlerContext;
import java.util.concurrent.TimeUnit;
public interface CancelableScheduler {
void update(ChannelHandlerContext ctx);
void cancel(SchedulerKey key);
void scheduleCallback(SchedulerKey key, Runnable runnable, long delay, TimeUnit unit);
void schedule(Runnable runnable, long delay, TimeUnit unit);
void schedule(SchedulerKey key, Runnable runnable, long delay, TimeUnit unit);
void shutdown();
}

112
fine-socketio/src/com/fr/third/socketio/scheduler/HashedWheelScheduler.java

@ -0,0 +1,112 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.scheduler;
import java.util.Map;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.HashedWheelTimer;
import io.netty.util.Timeout;
import io.netty.util.TimerTask;
import io.netty.util.internal.PlatformDependent;
public class HashedWheelScheduler implements CancelableScheduler {
private final Map<SchedulerKey, Timeout> scheduledFutures = PlatformDependent.newConcurrentHashMap();
private final HashedWheelTimer executorService;
public HashedWheelScheduler() {
executorService = new HashedWheelTimer();
}
public HashedWheelScheduler(ThreadFactory threadFactory) {
executorService = new HashedWheelTimer(threadFactory);
}
private volatile ChannelHandlerContext ctx;
@Override
public void update(ChannelHandlerContext ctx) {
this.ctx = ctx;
}
@Override
public void cancel(SchedulerKey key) {
Timeout timeout = scheduledFutures.remove(key);
if (timeout != null) {
timeout.cancel();
}
}
@Override
public void schedule(final Runnable runnable, long delay, TimeUnit unit) {
executorService.newTimeout(new TimerTask() {
@Override
public void run(Timeout timeout) throws Exception {
runnable.run();
}
}, delay, unit);
}
@Override
public void scheduleCallback(final SchedulerKey key, final Runnable runnable, long delay, TimeUnit unit) {
Timeout timeout = executorService.newTimeout(new TimerTask() {
@Override
public void run(Timeout timeout) throws Exception {
ctx.executor().execute(new Runnable() {
@Override
public void run() {
try {
runnable.run();
} finally {
scheduledFutures.remove(key);
}
}
});
}
}, delay, unit);
if (!timeout.isExpired()) {
scheduledFutures.put(key, timeout);
}
}
@Override
public void schedule(final SchedulerKey key, final Runnable runnable, long delay, TimeUnit unit) {
Timeout timeout = executorService.newTimeout(new TimerTask() {
@Override
public void run(Timeout timeout) throws Exception {
try {
runnable.run();
} finally {
scheduledFutures.remove(key);
}
}
}, delay, unit);
if (!timeout.isExpired()) {
scheduledFutures.put(key, timeout);
}
}
@Override
public void shutdown() {
executorService.stop();
}
}

133
fine-socketio/src/com/fr/third/socketio/scheduler/HashedWheelTimeoutScheduler.java

@ -0,0 +1,133 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Modified version of HashedWheelScheduler specially for timeouts handling.
* Difference:
* - handling old timeout with same key after adding new one
* fixes multithreaded problem that appears in highly concurrent non-atomic sequence cancel() -> schedule()
*
* (c) Alim Akbashev, 2015-02-11
*/
package com.fr.third.socketio.scheduler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.HashedWheelTimer;
import io.netty.util.Timeout;
import io.netty.util.TimerTask;
import io.netty.util.internal.PlatformDependent;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
public class HashedWheelTimeoutScheduler implements CancelableScheduler {
private final ConcurrentMap<SchedulerKey, Timeout> scheduledFutures = PlatformDependent.newConcurrentHashMap();
private final HashedWheelTimer executorService;
private volatile ChannelHandlerContext ctx;
public HashedWheelTimeoutScheduler() {
executorService = new HashedWheelTimer();
}
public HashedWheelTimeoutScheduler(ThreadFactory threadFactory) {
executorService = new HashedWheelTimer(threadFactory);
}
@Override
public void update(ChannelHandlerContext ctx) {
this.ctx = ctx;
}
@Override
public void cancel(SchedulerKey key) {
Timeout timeout = scheduledFutures.remove(key);
if (timeout != null) {
timeout.cancel();
}
}
@Override
public void schedule(final Runnable runnable, long delay, TimeUnit unit) {
executorService.newTimeout(new TimerTask() {
@Override
public void run(Timeout timeout) throws Exception {
runnable.run();
}
}, delay, unit);
}
@Override
public void scheduleCallback(final SchedulerKey key, final Runnable runnable, long delay, TimeUnit unit) {
Timeout timeout = executorService.newTimeout(new TimerTask() {
@Override
public void run(Timeout timeout) throws Exception {
ctx.executor().execute(new Runnable() {
@Override
public void run() {
try {
runnable.run();
} finally {
scheduledFutures.remove(key);
}
}
});
}
}, delay, unit);
replaceScheduledFuture(key, timeout);
}
@Override
public void schedule(final SchedulerKey key, final Runnable runnable, long delay, TimeUnit unit) {
Timeout timeout = executorService.newTimeout(new TimerTask() {
@Override
public void run(Timeout timeout) throws Exception {
try {
runnable.run();
} finally {
scheduledFutures.remove(key);
}
}
}, delay, unit);
replaceScheduledFuture(key, timeout);
}
@Override
public void shutdown() {
executorService.stop();
}
private void replaceScheduledFuture(final SchedulerKey key, final Timeout newTimeout) {
final Timeout oldTimeout;
if (newTimeout.isExpired()) {
// no need to put already expired timeout to scheduledFutures map.
// simply remove old timeout
oldTimeout = scheduledFutures.remove(key);
} else {
oldTimeout = scheduledFutures.put(key, newTimeout);
}
// if there was old timeout, cancel it
if (oldTimeout != null) {
oldTimeout.cancel();
}
}
}

60
fine-socketio/src/com/fr/third/socketio/scheduler/SchedulerKey.java

@ -0,0 +1,60 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.scheduler;
public class SchedulerKey {
public enum Type {PING_TIMEOUT, ACK_TIMEOUT, UPGRADE_TIMEOUT};
private final Type type;
private final Object sessionId;
public SchedulerKey(Type type, Object sessionId) {
this.type = type;
this.sessionId = sessionId;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((sessionId == null) ? 0 : sessionId.hashCode());
result = prime * result + ((type == null) ? 0 : type.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
SchedulerKey other = (SchedulerKey) obj;
if (sessionId == null) {
if (other.sessionId != null)
return false;
} else if (!sessionId.equals(other.sessionId))
return false;
if (type != other.type)
return false;
return true;
}
}

41
fine-socketio/src/com/fr/third/socketio/store/MemoryPubSubStore.java

@ -0,0 +1,41 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.store;
import com.fr.third.socketio.store.pubsub.PubSubListener;
import com.fr.third.socketio.store.pubsub.PubSubMessage;
import com.fr.third.socketio.store.pubsub.PubSubStore;
import com.fr.third.socketio.store.pubsub.PubSubType;
public class MemoryPubSubStore implements PubSubStore {
@Override
public void publish(PubSubType type, PubSubMessage msg) {
}
@Override
public <T extends PubSubMessage> void subscribe(PubSubType type, PubSubListener<T> listener, Class<T> clazz) {
}
@Override
public void unsubscribe(PubSubType type) {
}
@Override
public void shutdown() {
}
}

46
fine-socketio/src/com/fr/third/socketio/store/MemoryStore.java

@ -0,0 +1,46 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.store;
import io.netty.util.internal.PlatformDependent;
import java.util.Map;
public class MemoryStore implements Store {
private final Map<String, Object> store = PlatformDependent.newConcurrentHashMap();
@Override
public void set(String key, Object value) {
store.put(key, value);
}
@Override
public <T> T get(String key) {
return (T) store.get(key);
}
@Override
public boolean has(String key) {
return store.containsKey(key);
}
@Override
public void del(String key) {
store.remove(key);
}
}

53
fine-socketio/src/com/fr/third/socketio/store/MemoryStoreFactory.java

@ -0,0 +1,53 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.store;
import com.fr.third.socketio.store.pubsub.BaseStoreFactory;
import com.fr.third.socketio.store.pubsub.PubSubStore;
import io.netty.util.internal.PlatformDependent;
import java.util.Map;
import java.util.UUID;
public class MemoryStoreFactory extends BaseStoreFactory {
private final MemoryPubSubStore pubSubMemoryStore = new MemoryPubSubStore();
@Override
public Store createStore(UUID sessionId) {
return new MemoryStore();
}
@Override
public PubSubStore pubSubStore() {
return pubSubMemoryStore;
}
@Override
public void shutdown() {
}
@Override
public String toString() {
return getClass().getSimpleName() + " (local session store only)";
}
@Override
public <K, V> Map<K, V> createMap(String name) {
return PlatformDependent.newConcurrentHashMap();
}
}

90
fine-socketio/src/com/fr/third/socketio/store/RedissonPubSubStore.java

@ -0,0 +1,90 @@
///**
// * Copyright 2012 Nikita Koksharov
// *
// * Licensed under the Apache License, Version 2.0 (the "License");
// * you may not use this file except in compliance with the License.
// * You may obtain a copy of the License at
// *
// * http://www.apache.org/licenses/LICENSE-2.0
// *
// * Unless required by applicable law or agreed to in writing, software
// * distributed under the License is distributed on an "AS IS" BASIS,
// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// * See the License for the specific language governing permissions and
// * limitations under the License.
// */
//package com.fr.third.socketio.store;
//
//import java.util.Queue;
//import java.util.concurrent.ConcurrentLinkedQueue;
//import java.util.concurrent.ConcurrentMap;
//
//import com.fr.third.socketio.store.pubsub.PubSubListener;
//import com.fr.third.socketio.store.pubsub.PubSubMessage;
//import com.fr.third.socketio.store.pubsub.PubSubStore;
//import com.fr.third.socketio.store.pubsub.PubSubType;
//import org.redisson.api.RTopic;
//import org.redisson.api.RedissonClient;
//import org.redisson.api.listener.MessageListener;
//
//import io.netty.util.internal.PlatformDependent;
//
//public class RedissonPubSubStore implements PubSubStore {
//
// private final RedissonClient redissonPub;
// private final RedissonClient redissonSub;
// private final Long nodeId;
//
// private final ConcurrentMap<String, Queue<Integer>> map = PlatformDependent.newConcurrentHashMap();
//
// public RedissonPubSubStore(RedissonClient redissonPub, RedissonClient redissonSub, Long nodeId) {
// this.redissonPub = redissonPub;
// this.redissonSub = redissonSub;
// this.nodeId = nodeId;
// }
//
// @Override
// public void publish(PubSubType type, PubSubMessage msg) {
// msg.setNodeId(nodeId);
// redissonPub.getTopic(type.toString()).publish(msg);
// }
//
// @Override
// public <T extends PubSubMessage> void subscribe(PubSubType type, final PubSubListener<T> listener, Class<T> clazz) {
// String name = type.toString();
// RTopic<T> topic = redissonSub.getTopic(name);
// int regId = topic.addListener(new MessageListener<T>() {
// @Override
// public void onMessage(String channel, T msg) {
// if (!nodeId.equals(msg.getNodeId())) {
// listener.onMessage(msg);
// }
// }
// });
//
// Queue<Integer> list = map.get(name);
// if (list == null) {
// list = new ConcurrentLinkedQueue<Integer>();
// Queue<Integer> oldList = map.putIfAbsent(name, list);
// if (oldList != null) {
// list = oldList;
// }
// }
// list.add(regId);
// }
//
// @Override
// public void unsubscribe(PubSubType type) {
// String name = type.toString();
// Queue<Integer> regIds = map.remove(name);
// RTopic<Object> topic = redissonSub.getTopic(name);
// for (Integer id : regIds) {
// topic.removeListener(id);
// }
// }
//
// @Override
// public void shutdown() {
// }
//
//}

51
fine-socketio/src/com/fr/third/socketio/store/RedissonStore.java

@ -0,0 +1,51 @@
///**
// * Copyright 2012 Nikita Koksharov
// *
// * Licensed under the Apache License, Version 2.0 (the "License");
// * you may not use this file except in compliance with the License.
// * You may obtain a copy of the License at
// *
// * http://www.apache.org/licenses/LICENSE-2.0
// *
// * Unless required by applicable law or agreed to in writing, software
// * distributed under the License is distributed on an "AS IS" BASIS,
// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// * See the License for the specific language governing permissions and
// * limitations under the License.
// */
//package com.fr.third.socketio.store;
//
//import java.util.Map;
//import java.util.UUID;
//
//import org.redisson.api.RedissonClient;
//
//public class RedissonStore implements Store {
//
// private final Map<String, Object> map;
//
// public RedissonStore(UUID sessionId, RedissonClient redisson) {
// this.map = redisson.getMap(sessionId.toString());
// }
//
// @Override
// public void set(String key, Object value) {
// map.put(key, value);
// }
//
// @Override
// public <T> T get(String key) {
// return (T) map.get(key);
// }
//
// @Override
// public boolean has(String key) {
// return map.containsKey(key);
// }
//
// @Override
// public void del(String key) {
// map.remove(key);
// }
//
//}

76
fine-socketio/src/com/fr/third/socketio/store/RedissonStoreFactory.java

@ -0,0 +1,76 @@
///**
// * Copyright 2012 Nikita Koksharov
// *
// * Licensed under the Apache License, Version 2.0 (the "License");
// * you may not use this file except in compliance with the License.
// * You may obtain a copy of the License at
// *
// * http://www.apache.org/licenses/LICENSE-2.0
// *
// * Unless required by applicable law or agreed to in writing, software
// * distributed under the License is distributed on an "AS IS" BASIS,
// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// * See the License for the specific language governing permissions and
// * limitations under the License.
// */
//package com.fr.third.socketio.store;
//
//import java.util.Map;
//import java.util.UUID;
//
//import com.fr.third.socketio.store.pubsub.BaseStoreFactory;
//import com.fr.third.socketio.store.pubsub.PubSubStore;
//import org.redisson.Redisson;
//import org.redisson.api.RedissonClient;
//
//public class RedissonStoreFactory extends BaseStoreFactory {
//
// private final RedissonClient redisClient;
// private final RedissonClient redisPub;
// private final RedissonClient redisSub;
//
// private final PubSubStore pubSubStore;
//
// public RedissonStoreFactory() {
// this(Redisson.create());
// }
//
// public RedissonStoreFactory(RedissonClient redisson) {
// this.redisClient = redisson;
// this.redisPub = redisson;
// this.redisSub = redisson;
//
// this.pubSubStore = new RedissonPubSubStore(redisPub, redisSub, getNodeId());
// }
//
// public RedissonStoreFactory(Redisson redisClient, Redisson redisPub, Redisson redisSub) {
// this.redisClient = redisClient;
// this.redisPub = redisPub;
// this.redisSub = redisSub;
//
// this.pubSubStore = new RedissonPubSubStore(redisPub, redisSub, getNodeId());
// }
//
// @Override
// public Store createStore(UUID sessionId) {
// return new RedissonStore(sessionId, redisClient);
// }
//
// @Override
// public PubSubStore pubSubStore() {
// return pubSubStore;
// }
//
// @Override
// public void shutdown() {
// redisClient.shutdown();
// redisPub.shutdown();
// redisSub.shutdown();
// }
//
// @Override
// public <K, V> Map<K, V> createMap(String name) {
// return redisClient.getMap(name);
// }
//
//}

29
fine-socketio/src/com/fr/third/socketio/store/Store.java

@ -0,0 +1,29 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.store;
public interface Store {
void set(String key, Object val);
<T> T get(String key);
boolean has(String key);
void del(String key);
}

44
fine-socketio/src/com/fr/third/socketio/store/StoreFactory.java

@ -0,0 +1,44 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.store;
import java.util.Map;
import java.util.UUID;
import com.fr.third.socketio.Disconnectable;
import com.fr.third.socketio.handler.AuthorizeHandler;
import com.fr.third.socketio.namespace.NamespacesHub;
import com.fr.third.socketio.protocol.JsonSupport;
import com.fr.third.socketio.store.pubsub.PubSubStore;
/**
*
* Creates a client Store and PubSubStore
*
*/
public interface StoreFactory extends Disconnectable {
PubSubStore pubSubStore();
<K, V> Map<K, V> createMap(String name);
Store createStore(UUID sessionId);
void init(NamespacesHub namespacesHub, AuthorizeHandler authorizeHandler, JsonSupport jsonSupport);
void shutdown();
}

97
fine-socketio/src/com/fr/third/socketio/store/pubsub/BaseStoreFactory.java

@ -0,0 +1,97 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.store.pubsub;
import com.fr.third.socketio.handler.AuthorizeHandler;
import com.fr.third.socketio.handler.ClientHead;
import com.fr.third.socketio.namespace.NamespacesHub;
import com.fr.third.socketio.protocol.JsonSupport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fr.third.socketio.store.StoreFactory;
public abstract class BaseStoreFactory implements StoreFactory {
private final Logger log = LoggerFactory.getLogger(getClass());
private Long nodeId = (long) (Math.random() * 1000000);
protected Long getNodeId() {
return nodeId;
}
@Override
public void init(final NamespacesHub namespacesHub, final AuthorizeHandler authorizeHandler, JsonSupport jsonSupport) {
pubSubStore().subscribe(PubSubType.DISCONNECT, new PubSubListener<DisconnectMessage>() {
@Override
public void onMessage(DisconnectMessage msg) {
log.debug("{} sessionId: {}", PubSubType.DISCONNECT, msg.getSessionId());
}
}, DisconnectMessage.class);
pubSubStore().subscribe(PubSubType.CONNECT, new PubSubListener<ConnectMessage>() {
@Override
public void onMessage(ConnectMessage msg) {
authorizeHandler.connect(msg.getSessionId());
log.debug("{} sessionId: {}", PubSubType.CONNECT, msg.getSessionId());
}
}, ConnectMessage.class);
pubSubStore().subscribe(PubSubType.DISPATCH, new PubSubListener<DispatchMessage>() {
@Override
public void onMessage(DispatchMessage msg) {
String name = msg.getRoom();
namespacesHub.get(msg.getNamespace()).dispatch(name, msg.getPacket());
log.debug("{} packet: {}", PubSubType.DISPATCH, msg.getPacket());
}
}, DispatchMessage.class);
pubSubStore().subscribe(PubSubType.JOIN, new PubSubListener<JoinLeaveMessage>() {
@Override
public void onMessage(JoinLeaveMessage msg) {
String name = msg.getRoom();
namespacesHub.get(msg.getNamespace()).join(name, msg.getSessionId());
log.debug("{} sessionId: {}", PubSubType.JOIN, msg.getSessionId());
}
}, JoinLeaveMessage.class);
pubSubStore().subscribe(PubSubType.LEAVE, new PubSubListener<JoinLeaveMessage>() {
@Override
public void onMessage(JoinLeaveMessage msg) {
String name = msg.getRoom();
namespacesHub.get(msg.getNamespace()).leave(name, msg.getSessionId());
log.debug("{} sessionId: {}", PubSubType.LEAVE, msg.getSessionId());
}
}, JoinLeaveMessage.class);
}
@Override
public abstract PubSubStore pubSubStore();
@Override
public void onDisconnect(ClientHead client) {
}
@Override
public String toString() {
return getClass().getSimpleName() + " (distributed session store, distributed publish/subscribe)";
}
}

38
fine-socketio/src/com/fr/third/socketio/store/pubsub/ConnectMessage.java

@ -0,0 +1,38 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.store.pubsub;
import java.util.UUID;
public class ConnectMessage extends PubSubMessage {
private static final long serialVersionUID = 3108918714495865101L;
private UUID sessionId;
public ConnectMessage() {
}
public ConnectMessage(UUID sessionId) {
super();
this.sessionId = sessionId;
}
public UUID getSessionId() {
return sessionId;
}
}

38
fine-socketio/src/com/fr/third/socketio/store/pubsub/DisconnectMessage.java

@ -0,0 +1,38 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.store.pubsub;
import java.util.UUID;
public class DisconnectMessage extends PubSubMessage {
private static final long serialVersionUID = -2763553673397520368L;
private UUID sessionId;
public DisconnectMessage() {
}
public DisconnectMessage(UUID sessionId) {
super();
this.sessionId = sessionId;
}
public UUID getSessionId() {
return sessionId;
}
}

49
fine-socketio/src/com/fr/third/socketio/store/pubsub/DispatchMessage.java

@ -0,0 +1,49 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.store.pubsub;
import com.fr.third.socketio.protocol.Packet;
public class DispatchMessage extends PubSubMessage {
private static final long serialVersionUID = 6692047718303934349L;
private String room;
private String namespace;
private Packet packet;
public DispatchMessage() {
}
public DispatchMessage(String room, Packet packet, String namespace) {
this.room = room;
this.packet = packet;
this.namespace = namespace;
}
public String getNamespace() {
return namespace;
}
public Packet getPacket() {
return packet;
}
public String getRoom() {
return room;
}
}

50
fine-socketio/src/com/fr/third/socketio/store/pubsub/JoinLeaveMessage.java

@ -0,0 +1,50 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.store.pubsub;
import java.util.UUID;
public class JoinLeaveMessage extends PubSubMessage {
private static final long serialVersionUID = -944515928988033174L;
private UUID sessionId;
private String namespace;
private String room;
public JoinLeaveMessage() {
}
public JoinLeaveMessage(UUID id, String room, String namespace) {
super();
this.sessionId = id;
this.room = room;
this.namespace = namespace;
}
public String getNamespace() {
return namespace;
}
public UUID getSessionId() {
return sessionId;
}
public String getRoom() {
return room;
}
}

23
fine-socketio/src/com/fr/third/socketio/store/pubsub/PubSubListener.java

@ -0,0 +1,23 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.store.pubsub;
public interface PubSubListener<T> {
void onMessage(T data);
}

34
fine-socketio/src/com/fr/third/socketio/store/pubsub/PubSubMessage.java

@ -0,0 +1,34 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.store.pubsub;
import java.io.Serializable;
public abstract class PubSubMessage implements Serializable {
private static final long serialVersionUID = -8789343104393884987L;
private Long nodeId;
public Long getNodeId() {
return nodeId;
}
public void setNodeId(Long nodeId) {
this.nodeId = nodeId;
}
}

29
fine-socketio/src/com/fr/third/socketio/store/pubsub/PubSubStore.java

@ -0,0 +1,29 @@
/**
* Copyright 2012 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fr.third.socketio.store.pubsub;
public interface PubSubStore {
void publish(PubSubType type, PubSubMessage msg);
<T extends PubSubMessage> void subscribe(PubSubType type, PubSubListener<T> listener, Class<T> clazz);
void unsubscribe(PubSubType type);
void shutdown();
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save