Rock
1 year ago
107 changed files with 2 additions and 9497 deletions
@ -1,6 +0,0 @@ |
|||||||
*.iml |
|
||||||
.idea/ |
|
||||||
.DS_Store |
|
||||||
.project |
|
||||||
.classpath |
|
||||||
*.gradle |
|
Binary file not shown.
@ -1,52 +0,0 @@ |
|||||||
<?xml version="1.0" encoding="UTF-8"?> |
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" |
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |
|
||||||
<modelVersion>4.0.0</modelVersion> |
|
||||||
|
|
||||||
<parent> |
|
||||||
<groupId>com.fr.third</groupId> |
|
||||||
<artifactId>step6</artifactId> |
|
||||||
<version>${revision}</version> |
|
||||||
<relativePath>../base-third-project/base-third-step6</relativePath> |
|
||||||
</parent> |
|
||||||
|
|
||||||
<artifactId>fine-socketio</artifactId> |
|
||||||
<version>${revision}</version> |
|
||||||
|
|
||||||
<dependencies> |
|
||||||
<dependency> |
|
||||||
<groupId>com.fr.essential</groupId> |
|
||||||
<artifactId>fine-jackson</artifactId> |
|
||||||
<version>${essentialVersion}</version> |
|
||||||
</dependency> |
|
||||||
<dependency> |
|
||||||
<groupId>com.fr.essential</groupId> |
|
||||||
<artifactId>fine-slf4j-api</artifactId> |
|
||||||
<version>${essentialVersion}</version> |
|
||||||
</dependency> |
|
||||||
<dependency> |
|
||||||
<groupId>com.fr.third</groupId> |
|
||||||
<artifactId>fine-redisson</artifactId> |
|
||||||
<version>${revision}</version> |
|
||||||
</dependency> |
|
||||||
<dependency> |
|
||||||
<groupId>com.fr.third</groupId> |
|
||||||
<artifactId>fine-spring</artifactId> |
|
||||||
<version>${revision}</version> |
|
||||||
</dependency> |
|
||||||
<dependency> |
|
||||||
<groupId>com.fr.essential</groupId> |
|
||||||
<artifactId>fine-common</artifactId> |
|
||||||
<version>${essentialVersion}</version> |
|
||||||
</dependency> |
|
||||||
<!-->以下是lib的本地jar包依赖<--> |
|
||||||
<dependency> |
|
||||||
<groupId>com.fr.third</groupId> |
|
||||||
<artifactId>netty</artifactId> |
|
||||||
<version>local</version> |
|
||||||
<scope>system</scope> |
|
||||||
<systemPath>${basedir}/lib/netty-all-4.1.22.Final.jar</systemPath> |
|
||||||
</dependency> |
|
||||||
</dependencies> |
|
||||||
</project> |
|
@ -1,91 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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 com.fr.third.socketio.VoidAckCallback |
|
||||||
* @see com.fr.third.socketio.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; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,39 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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 |
|
||||||
|
|
||||||
} |
|
@ -1,90 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,28 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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); |
|
||||||
|
|
||||||
} |
|
@ -1,82 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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(); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
@ -1,38 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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; |
|
||||||
|
|
||||||
import java.util.Collection; |
|
||||||
|
|
||||||
/** |
|
||||||
* broadcast interface
|
|
||||||
* |
|
||||||
*/ |
|
||||||
public interface BroadcastOperations extends ClientOperations { |
|
||||||
|
|
||||||
Collection<SocketIOClient> getClients(); |
|
||||||
|
|
||||||
<T> void send(Packet packet, BroadcastAckCallback<T> ackCallback); |
|
||||||
|
|
||||||
void sendEvent(String name, SocketIOClient excludedClient, Object... data); |
|
||||||
|
|
||||||
<T> void sendEvent(String name, Object data, BroadcastAckCallback<T> ackCallback); |
|
||||||
|
|
||||||
<T> void sendEvent(String name, Object data, SocketIOClient excludedClient, BroadcastAckCallback<T> ackCallback); |
|
||||||
|
|
||||||
} |
|
@ -1,49 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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); |
|
||||||
|
|
||||||
} |
|
@ -1,656 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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.InputStream; |
|
||||||
import java.util.Arrays; |
|
||||||
import java.util.Collections; |
|
||||||
import java.util.List; |
|
||||||
|
|
||||||
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.listener.interceptor.EventInterceptor; |
|
||||||
import com.fr.third.socketio.listener.interceptor.Interceptor; |
|
||||||
import com.fr.third.socketio.protocol.JsonSupport; |
|
||||||
import com.fr.third.socketio.store.MemoryStoreFactory; |
|
||||||
import com.fr.third.socketio.store.RedissonStoreFactory; |
|
||||||
import com.fr.third.socketio.store.StoreFactory; |
|
||||||
|
|
||||||
import javax.net.ssl.KeyManagerFactory; |
|
||||||
|
|
||||||
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; |
|
||||||
|
|
||||||
private boolean randomSession = false; |
|
||||||
|
|
||||||
private String accessControlAllowHeaders = null; |
|
||||||
|
|
||||||
private List<Interceptor> connectInterceptors = Collections.emptyList(); |
|
||||||
private List<Interceptor> disconnectInterceptors = Collections.emptyList(); |
|
||||||
private List<EventInterceptor> eventInterceptors = Collections.emptyList(); |
|
||||||
|
|
||||||
private String[] nettyProtocols = new String[]{}; |
|
||||||
|
|
||||||
private String[] nettyCiphers = new String[]{}; |
|
||||||
|
|
||||||
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.fr.third.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()); |
|
||||||
setRandomSession(conf.randomSession); |
|
||||||
|
|
||||||
setAccessControlAllowHeaders(conf.getAccessControlAllowHeaders()); |
|
||||||
setConnectInterceptors(conf.getConnectInterceptors().toArray(new Interceptor[0])); |
|
||||||
setDisconnectInterceptors(conf.getDisconnectInterceptors().toArray(new Interceptor[0])); |
|
||||||
setEventInterceptors(conf.getEventInterceptors().toArray(new EventInterceptor[0])); |
|
||||||
|
|
||||||
setNettyProtocols(conf.getNettyProtocols()); |
|
||||||
setNettyCiphers(conf.getNettyCiphers()); |
|
||||||
} |
|
||||||
|
|
||||||
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 |
|
||||||
* @see RedissonStoreFactory |
|
||||||
*/ |
|
||||||
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; |
|
||||||
} |
|
||||||
|
|
||||||
public boolean isRandomSession() { |
|
||||||
return randomSession; |
|
||||||
} |
|
||||||
|
|
||||||
public void setRandomSession(boolean randomSession) { |
|
||||||
this.randomSession = randomSession; |
|
||||||
} |
|
||||||
|
|
||||||
public String getAccessControlAllowHeaders() { |
|
||||||
return accessControlAllowHeaders; |
|
||||||
} |
|
||||||
|
|
||||||
public void setAccessControlAllowHeaders(String accessControlAllowHeaders) { |
|
||||||
this.accessControlAllowHeaders = accessControlAllowHeaders; |
|
||||||
} |
|
||||||
|
|
||||||
public List<Interceptor> getConnectInterceptors() { |
|
||||||
return connectInterceptors; |
|
||||||
} |
|
||||||
|
|
||||||
public void setConnectInterceptors(Interceptor[] connectInterceptors) { |
|
||||||
this.connectInterceptors = Arrays.asList(connectInterceptors); |
|
||||||
} |
|
||||||
|
|
||||||
public List<Interceptor> getDisconnectInterceptors() { |
|
||||||
return disconnectInterceptors; |
|
||||||
} |
|
||||||
|
|
||||||
public void setDisconnectInterceptors(Interceptor[] disconnectInterceptors) { |
|
||||||
this.disconnectInterceptors = Arrays.asList(disconnectInterceptors); |
|
||||||
} |
|
||||||
|
|
||||||
public List<EventInterceptor> getEventInterceptors() { |
|
||||||
return eventInterceptors; |
|
||||||
} |
|
||||||
|
|
||||||
public void setEventInterceptors(EventInterceptor[] eventInterceptors) { |
|
||||||
this.eventInterceptors = Arrays.asList(eventInterceptors); |
|
||||||
} |
|
||||||
|
|
||||||
public String[] getNettyProtocols() { |
|
||||||
return nettyProtocols; |
|
||||||
} |
|
||||||
|
|
||||||
public void setNettyProtocols(String[] nettyProtocols) { |
|
||||||
this.nettyProtocols = nettyProtocols; |
|
||||||
} |
|
||||||
|
|
||||||
public String[] getNettyCiphers() { |
|
||||||
return nettyCiphers; |
|
||||||
} |
|
||||||
|
|
||||||
public void setNettyCiphers(String[] nettyCiphers) { |
|
||||||
this.nettyCiphers = nettyCiphers; |
|
||||||
} |
|
||||||
} |
|
@ -1,26 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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); |
|
||||||
|
|
||||||
} |
|
@ -1,20 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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 { |
|
||||||
|
|
||||||
} |
|
@ -1,122 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,87 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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 com.fr.third.slf4j.Logger; |
|
||||||
import com.fr.third.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(); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,117 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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; |
|
||||||
|
|
||||||
import java.util.Collection; |
|
||||||
import java.util.HashSet; |
|
||||||
import java.util.Set; |
|
||||||
|
|
||||||
/** |
|
||||||
* author: liangjiaqi |
|
||||||
* date: 2020/8/8 6:02 PM |
|
||||||
*/ |
|
||||||
public class MultiRoomBroadcastOperations implements BroadcastOperations { |
|
||||||
|
|
||||||
private Collection<BroadcastOperations> broadcastOperations; |
|
||||||
|
|
||||||
public MultiRoomBroadcastOperations(Collection<BroadcastOperations> broadcastOperations) { |
|
||||||
this.broadcastOperations = broadcastOperations; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public Collection<SocketIOClient> getClients() { |
|
||||||
Set<SocketIOClient> clients = new HashSet<SocketIOClient>(); |
|
||||||
if( this.broadcastOperations == null || this.broadcastOperations.size() == 0 ) { |
|
||||||
return clients; |
|
||||||
} |
|
||||||
for( BroadcastOperations b : this.broadcastOperations ) { |
|
||||||
clients.addAll( b.getClients() ); |
|
||||||
} |
|
||||||
return clients; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public <T> void send(Packet packet, BroadcastAckCallback<T> ackCallback) { |
|
||||||
if( this.broadcastOperations == null || this.broadcastOperations.size() == 0 ) { |
|
||||||
return; |
|
||||||
} |
|
||||||
for( BroadcastOperations b : this.broadcastOperations ) { |
|
||||||
b.send( packet, ackCallback ); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void sendEvent(String name, SocketIOClient excludedClient, Object... data) { |
|
||||||
if( this.broadcastOperations == null || this.broadcastOperations.size() == 0 ) { |
|
||||||
return; |
|
||||||
} |
|
||||||
for( BroadcastOperations b : this.broadcastOperations ) { |
|
||||||
b.sendEvent( name, excludedClient, data ); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public <T> void sendEvent(String name, Object data, BroadcastAckCallback<T> ackCallback) { |
|
||||||
if( this.broadcastOperations == null || this.broadcastOperations.size() == 0 ) { |
|
||||||
return; |
|
||||||
} |
|
||||||
for( BroadcastOperations b : this.broadcastOperations ) { |
|
||||||
b.sendEvent( name, data, ackCallback ); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public <T> void sendEvent(String name, Object data, SocketIOClient excludedClient, BroadcastAckCallback<T> ackCallback) { |
|
||||||
if( this.broadcastOperations == null || this.broadcastOperations.size() == 0 ) { |
|
||||||
return; |
|
||||||
} |
|
||||||
for( BroadcastOperations b : this.broadcastOperations ) { |
|
||||||
b.sendEvent( name, data, excludedClient, ackCallback ); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void send(Packet packet) { |
|
||||||
if( this.broadcastOperations == null || this.broadcastOperations.size() == 0 ) { |
|
||||||
return; |
|
||||||
} |
|
||||||
for( BroadcastOperations b : this.broadcastOperations ) { |
|
||||||
b.send( packet ); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void disconnect() { |
|
||||||
if( this.broadcastOperations == null || this.broadcastOperations.size() == 0 ) { |
|
||||||
return; |
|
||||||
} |
|
||||||
for( BroadcastOperations b : this.broadcastOperations ) { |
|
||||||
b.disconnect(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void sendEvent(String name, Object... data) { |
|
||||||
if( this.broadcastOperations == null || this.broadcastOperations.size() == 0 ) { |
|
||||||
return; |
|
||||||
} |
|
||||||
for( BroadcastOperations b : this.broadcastOperations ) { |
|
||||||
b.sendEvent( name, data ); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,35 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,69 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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(); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,123 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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.misc.IterableCollection; |
|
||||||
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; |
|
||||||
|
|
||||||
import java.util.Arrays; |
|
||||||
import java.util.Collection; |
|
||||||
|
|
||||||
/** |
|
||||||
* Author: liangjiaqi |
|
||||||
* Date: 2020/8/8 6:08 PM |
|
||||||
*/ |
|
||||||
public class SingleRoomBroadcastOperations implements BroadcastOperations { |
|
||||||
private final String namespace; |
|
||||||
private final String room; |
|
||||||
private final Iterable<SocketIOClient> clients; |
|
||||||
private final StoreFactory storeFactory; |
|
||||||
|
|
||||||
public SingleRoomBroadcastOperations(String namespace, String room, Iterable<SocketIOClient> clients, StoreFactory storeFactory) { |
|
||||||
super(); |
|
||||||
this.namespace = namespace; |
|
||||||
this.room = room; |
|
||||||
this.clients = clients; |
|
||||||
this.storeFactory = storeFactory; |
|
||||||
} |
|
||||||
|
|
||||||
private void dispatch(Packet packet) { |
|
||||||
this.storeFactory.pubSubStore().publish( |
|
||||||
PubSubType.DISPATCH, |
|
||||||
new DispatchMessage(this.room, packet, this.namespace)); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public Collection<SocketIOClient> getClients() { |
|
||||||
return new IterableCollection<SocketIOClient>(clients); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void send(Packet packet) { |
|
||||||
for (SocketIOClient client : clients) { |
|
||||||
client.send(packet); |
|
||||||
} |
|
||||||
dispatch(packet); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
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(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
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); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public <T> void sendEvent(String name, Object data, BroadcastAckCallback<T> ackCallback) { |
|
||||||
for (SocketIOClient client : clients) { |
|
||||||
client.sendEvent(name, ackCallback.createClientCallback(client), data); |
|
||||||
} |
|
||||||
ackCallback.loopFinished(); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
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(); |
|
||||||
} |
|
||||||
} |
|
@ -1,89 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,246 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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 com.fr.stable.ArrayUtils; |
|
||||||
import com.fr.third.slf4j.Logger; |
|
||||||
import com.fr.third.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); |
|
||||||
|
|
||||||
if (ArrayUtils.isNotEmpty(configuration.getNettyProtocols())) { |
|
||||||
engine.setEnabledProtocols(configuration.getNettyProtocols()); |
|
||||||
} |
|
||||||
|
|
||||||
if (ArrayUtils.isNotEmpty(configuration.getNettyCiphers())) { |
|
||||||
engine.setEnabledCipherSuites(configuration.getNettyCiphers()); |
|
||||||
} |
|
||||||
|
|
||||||
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(); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,112 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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(); |
|
||||||
|
|
||||||
} |
|
@ -1,50 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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); |
|
||||||
|
|
||||||
} |
|
@ -1,307 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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 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.ArrayList; |
|
||||||
import java.util.Collection; |
|
||||||
import java.util.List; |
|
||||||
import java.util.UUID; |
|
||||||
import java.util.concurrent.TimeUnit; |
|
||||||
|
|
||||||
import io.netty.util.concurrent.GenericFutureListener; |
|
||||||
import com.fr.third.slf4j.Logger; |
|
||||||
import com.fr.third.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() { |
|
||||||
Collection<SocketIONamespace> namespaces = namespacesHub.getAllNamespaces(); |
|
||||||
List<BroadcastOperations> list = new ArrayList<BroadcastOperations>(); |
|
||||||
BroadcastOperations broadcast = null; |
|
||||||
if( namespaces != null && namespaces.size() > 0 ) { |
|
||||||
for( SocketIONamespace n : namespaces ) { |
|
||||||
broadcast = n.getBroadcastOperations(); |
|
||||||
list.add( broadcast ); |
|
||||||
} |
|
||||||
} |
|
||||||
return new MultiRoomBroadcastOperations( list ); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 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) { |
|
||||||
Collection<SocketIONamespace> namespaces = namespacesHub.getAllNamespaces(); |
|
||||||
List<BroadcastOperations> list = new ArrayList<BroadcastOperations>(); |
|
||||||
BroadcastOperations broadcast = null; |
|
||||||
if( namespaces != null && namespaces.size() > 0 ) { |
|
||||||
for( SocketIONamespace n : namespaces ) { |
|
||||||
broadcast = n.getRoomOperations( room ); |
|
||||||
list.add( broadcast ); |
|
||||||
} |
|
||||||
} |
|
||||||
return new MultiRoomBroadcastOperations( list ); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Start server |
|
||||||
*/ |
|
||||||
public void start() { |
|
||||||
startAsync().syncUninterruptibly(); |
|
||||||
} |
|
||||||
|
|
||||||
public void start(GenericFutureListener<? extends Future<? super Void>> listener) { |
|
||||||
startAsync(listener).syncUninterruptibly(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Start server asynchronously |
|
||||||
* |
|
||||||
* @return void |
|
||||||
*/ |
|
||||||
public Future<Void> startAsync() { |
|
||||||
return startAsync(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()); |
|
||||||
} |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Start server asynchronously |
|
||||||
* |
|
||||||
* @return void |
|
||||||
*/ |
|
||||||
public Future<Void> startAsync(GenericFutureListener<? extends Future<? super Void>> listener) { |
|
||||||
|
|
||||||
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(listener); |
|
||||||
} |
|
||||||
|
|
||||||
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 void stopImmediately() { |
|
||||||
this.bossGroup.shutdownGracefully(0L, 1L, TimeUnit.SECONDS).syncUninterruptibly(); |
|
||||||
this.workerGroup.shutdownGracefully(0L, 1L, TimeUnit.SECONDS).syncUninterruptibly(); |
|
||||||
this.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); |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
} |
|
@ -1,45 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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"); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,39 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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(); |
|
||||||
|
|
||||||
} |
|
@ -1,183 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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.*; |
|
||||||
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 com.fr.third.socketio.scheduler.SchedulerKey.Type; |
|
||||||
import io.netty.util.internal.PlatformDependent; |
|
||||||
import com.fr.third.slf4j.Logger; |
|
||||||
import com.fr.third.slf4j.LoggerFactory; |
|
||||||
|
|
||||||
import java.util.List; |
|
||||||
import java.util.Map; |
|
||||||
import java.util.Set; |
|
||||||
import java.util.UUID; |
|
||||||
import java.util.concurrent.ConcurrentMap; |
|
||||||
import java.util.concurrent.TimeUnit; |
|
||||||
import java.util.concurrent.atomic.AtomicLong; |
|
||||||
|
|
||||||
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 ConcurrentMap<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.putIfAbsent(sessionId, ackEntry); |
|
||||||
if (oldAckEntry != null) { |
|
||||||
ackEntry = oldAckEntry; |
|
||||||
} |
|
||||||
} |
|
||||||
return ackEntry; |
|
||||||
} |
|
||||||
|
|
||||||
@SuppressWarnings("unchecked") |
|
||||||
public void onAck(SocketIOClient client, Packet packet) { |
|
||||||
AckSchedulerKey key = new AckSchedulerKey(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(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(Type.ACK_TIMEOUT, client.getSessionId(), index); |
|
||||||
scheduler.cancel(key); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,57 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,31 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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); |
|
||||||
|
|
||||||
} |
|
@ -1,36 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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 { |
|
||||||
|
|
||||||
} |
|
@ -1,66 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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()); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,35 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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 { |
|
||||||
|
|
||||||
} |
|
@ -1,66 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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()); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,45 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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(); |
|
||||||
|
|
||||||
} |
|
@ -1,154 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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()); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,105 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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 com.fr.third.slf4j.Logger; |
|
||||||
import com.fr.third.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); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,89 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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.slf4j.Logger; |
|
||||||
import com.fr.third.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; |
|
||||||
|
|
||||||
import com.fr.third.socketio.SocketIOServer; |
|
||||||
|
|
||||||
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; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,272 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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.slf4j.Logger; |
|
||||||
import com.fr.third.slf4j.LoggerFactory; |
|
||||||
|
|
||||||
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.ack.AckManager; |
|
||||||
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 com.fr.third.socketio.scheduler.SchedulerKey.Type; |
|
||||||
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(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(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 = null; |
|
||||||
if (configuration.isRandomSession()) { |
|
||||||
sessionId = UUID.randomUUID(); |
|
||||||
} else { |
|
||||||
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) { |
|
||||||
List<String> values = headers.getAll("io"); |
|
||||||
if (values.size() == 1) { |
|
||||||
try { |
|
||||||
return UUID.fromString(values.get(0)); |
|
||||||
} catch (IllegalArgumentException iaex) { |
|
||||||
log.warn("Malformed UUID received for session! io=" + values.get(0)); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
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(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()); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,298 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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.Configuration; |
|
||||||
import com.fr.third.socketio.DisconnectableHub; |
|
||||||
import com.fr.third.socketio.HandshakeData; |
|
||||||
import com.fr.third.socketio.Transport; |
|
||||||
import com.fr.third.socketio.ack.AckManager; |
|
||||||
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.scheduler.SchedulerKey.Type; |
|
||||||
import com.fr.third.socketio.store.Store; |
|
||||||
import com.fr.third.socketio.store.StoreFactory; |
|
||||||
import com.fr.third.socketio.transport.NamespaceClient; |
|
||||||
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; |
|
||||||
import com.fr.third.slf4j.Logger; |
|
||||||
import com.fr.third.slf4j.LoggerFactory; |
|
||||||
|
|
||||||
import java.net.SocketAddress; |
|
||||||
import java.util.*; |
|
||||||
import java.util.Map.Entry; |
|
||||||
import java.util.concurrent.TimeUnit; |
|
||||||
import java.util.concurrent.atomic.AtomicBoolean; |
|
||||||
|
|
||||||
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()); |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
/** |
|
||||||
* 这里多线程下,会有问题, |
|
||||||
* 因为 channel 不会变, 但是 state 可能因为多线程下调用 bindChannel |
|
||||||
* 从而导致 state.update , 致使真正 add() 时, |
|
||||||
* channel 和 state 得数据不一致。 |
|
||||||
* |
|
||||||
* 解决方法 |
|
||||||
* 1. 加锁 |
|
||||||
* 2. 为 clientbox 加一个子线程, 定时清除 disconnected 为 true 的值。 |
|
||||||
* |
|
||||||
* @param channel 频道 |
|
||||||
* @param transport 协议 |
|
||||||
*/ |
|
||||||
public synchronized 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); |
|
||||||
prevChannel.disconnect(); |
|
||||||
} |
|
||||||
clientsBox.add(channel, this); |
|
||||||
|
|
||||||
sendPackets(transport, channel); |
|
||||||
} |
|
||||||
|
|
||||||
public void releasePollingChannel(Channel channel) { |
|
||||||
TransportState state = channels.get(Transport.POLLING); |
|
||||||
if(channel.equals(state.getChannel())) { |
|
||||||
clientsBox.remove(channel); |
|
||||||
state.update(null); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
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(Type.PING_TIMEOUT, sessionId); |
|
||||||
disconnectScheduler.cancel(key); |
|
||||||
} |
|
||||||
|
|
||||||
public void schedulePingTimeout() { |
|
||||||
SchedulerKey key = new SchedulerKey(Type.PING_TIMEOUT, sessionId); |
|
||||||
disconnectScheduler.schedule(key, new TimeOutTask(this), 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()); |
|
||||||
|
|
||||||
// 清空到最后一个的时候, 断开连接。 调用 initializer.disconnect
|
|
||||||
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)); |
|
||||||
if(future != null) { |
|
||||||
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; |
|
||||||
} |
|
||||||
|
|
||||||
private class TimeOutTask implements Runnable{ |
|
||||||
|
|
||||||
ClientHead client; |
|
||||||
|
|
||||||
public TimeOutTask(ClientHead client) { |
|
||||||
|
|
||||||
assert client != null; |
|
||||||
this.client = client; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void run() { |
|
||||||
|
|
||||||
if (client != null && !client.disconnected.get()) { |
|
||||||
client.disconnect(); |
|
||||||
log.debug("{} removed due to ping timeout", client.getSessionId()); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,66 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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 io.netty.channel.Channel; |
|
||||||
import io.netty.util.internal.PlatformDependent; |
|
||||||
|
|
||||||
import java.util.Map; |
|
||||||
import java.util.UUID; |
|
||||||
|
|
||||||
import com.fr.third.socketio.HandshakeData; |
|
||||||
|
|
||||||
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); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,361 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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 com.fr.third.springframework.util.StringUtils.isEmpty; |
|
||||||
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 io.netty.util.concurrent.Future; |
|
||||||
import io.netty.util.concurrent.GenericFutureListener; |
|
||||||
import com.fr.third.slf4j.Logger; |
|
||||||
import com.fr.third.slf4j.LoggerFactory; |
|
||||||
|
|
||||||
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.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 static final String SEPARATOR_COMMA = ","; |
|
||||||
|
|
||||||
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(); |
|
||||||
addOriginAndAllowHeaders(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(); |
|
||||||
addOriginAndAllowHeaders(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 addOriginAndAllowHeaders(String origin, HttpResponse res) { |
|
||||||
|
|
||||||
if (version != null) { |
|
||||||
res.headers().add(HttpHeaderNames.SERVER, version); |
|
||||||
} |
|
||||||
|
|
||||||
if (origin != null) { |
|
||||||
String configOrigin = configuration.getOrigin(); |
|
||||||
if (!isEmpty(configOrigin) && !configOrigin.contains(origin)) { |
|
||||||
throw new IllegalArgumentException(); |
|
||||||
} |
|
||||||
res.headers().add(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN, origin); |
|
||||||
res.headers().add(HttpHeaderNames.ACCESS_CONTROL_ALLOW_CREDENTIALS, Boolean.TRUE); |
|
||||||
} |
|
||||||
|
|
||||||
// 使websocket请求可携带特定header
|
|
||||||
String allowHeaders = configuration.getAccessControlAllowHeaders(); |
|
||||||
// 取出现有access-control-allow-headers,去掉空格并拼接新的header到末尾。直接addHeader时IE 10/11会找不到。
|
|
||||||
if (!isEmpty(allowHeaders)) { |
|
||||||
String header = res.headers().get(HttpHeaderNames.ACCESS_CONTROL_ALLOW_HEADERS); |
|
||||||
if (!isEmpty(header)) { |
|
||||||
header = header.trim() + SEPARATOR_COMMA + configuration.getAccessControlAllowHeaders(); |
|
||||||
res.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_HEADERS, header); |
|
||||||
} else { |
|
||||||
res.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_HEADERS, configuration.getAccessControlAllowHeaders()); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@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(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,108 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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 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 com.fr.third.slf4j.Logger; |
|
||||||
import com.fr.third.slf4j.LoggerFactory; |
|
||||||
|
|
||||||
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; |
|
||||||
|
|
||||||
@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); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,120 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,34 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,28 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,48 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,57 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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 com.fr.third.slf4j.Logger; |
|
||||||
import com.fr.third.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); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,36 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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); |
|
||||||
|
|
||||||
} |
|
@ -1,24 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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); |
|
||||||
|
|
||||||
} |
|
@ -1,33 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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 |
|
||||||
* |
|
||||||
*/ |
|
||||||
void onData(SocketIOClient client, T data, AckRequest ackSender) throws Exception; |
|
||||||
|
|
||||||
} |
|
@ -1,57 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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 io.netty.channel.ChannelHandlerContext; |
|
||||||
|
|
||||||
import java.util.List; |
|
||||||
|
|
||||||
import com.fr.third.slf4j.Logger; |
|
||||||
import com.fr.third.slf4j.LoggerFactory; |
|
||||||
|
|
||||||
import com.fr.third.socketio.SocketIOClient; |
|
||||||
|
|
||||||
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; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,24 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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); |
|
||||||
|
|
||||||
} |
|
@ -1,36 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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 io.netty.channel.ChannelHandlerContext; |
|
||||||
|
|
||||||
import java.util.List; |
|
||||||
|
|
||||||
import com.fr.third.socketio.SocketIOClient; |
|
||||||
|
|
||||||
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; |
|
||||||
|
|
||||||
} |
|
@ -1,53 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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 io.netty.channel.ChannelHandlerContext; |
|
||||||
|
|
||||||
import java.util.List; |
|
||||||
|
|
||||||
import com.fr.third.socketio.SocketIOClient; |
|
||||||
|
|
||||||
/** |
|
||||||
* 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; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onPingException(Exception e, SocketIOClient client) { |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,26 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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> { |
|
||||||
|
|
||||||
} |
|
@ -1,24 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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); |
|
||||||
|
|
||||||
} |
|
@ -1,27 +0,0 @@ |
|||||||
package com.fr.third.socketio.listener.interceptor; |
|
||||||
|
|
||||||
import com.fr.third.socketio.AckRequest; |
|
||||||
import com.fr.third.socketio.SocketIOClient; |
|
||||||
|
|
||||||
import java.util.List; |
|
||||||
|
|
||||||
/** |
|
||||||
* Desc |
|
||||||
* |
|
||||||
* @author Elijah |
|
||||||
* created on 2021-04-26 |
|
||||||
*/ |
|
||||||
public interface EventInterceptor extends Interceptor { |
|
||||||
default void before(SocketIOClient client, List<Object> params, AckRequest ackRequest){ |
|
||||||
before(client); |
|
||||||
} |
|
||||||
|
|
||||||
default void after(SocketIOClient client, List<Object> params, AckRequest ackRequest){ |
|
||||||
after(client); |
|
||||||
} |
|
||||||
|
|
||||||
default boolean accept(String eventName, SocketIOClient client, List<Object> param){ |
|
||||||
return accept(client); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,23 +0,0 @@ |
|||||||
package com.fr.third.socketio.listener.interceptor; |
|
||||||
|
|
||||||
import com.fr.third.socketio.SocketIOClient; |
|
||||||
|
|
||||||
/** |
|
||||||
* Desc |
|
||||||
* |
|
||||||
* @author Elijah |
|
||||||
* created on 2021-05-06 |
|
||||||
*/ |
|
||||||
public interface Interceptor { |
|
||||||
default void before(SocketIOClient client){ |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
default void after(SocketIOClient client){ |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
default boolean accept(SocketIOClient client){ |
|
||||||
return true; |
|
||||||
} |
|
||||||
} |
|
@ -1,33 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,38 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,41 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,47 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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 io.netty.buffer.ByteBuf; |
|
||||||
|
|
||||||
import com.fr.third.socketio.Transport; |
|
||||||
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; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,26 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,26 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,56 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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> { |
|
||||||
|
|
||||||
private List<Iterable<T>> iterablesList; |
|
||||||
private Iterable<T>[] iterables; |
|
||||||
|
|
||||||
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()); |
|
||||||
} |
|
||||||
} |
|
||||||
return new CompositeIterator<T>(iterators.iterator()); |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
} |
|
@ -1,55 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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.Iterator; |
|
||||||
|
|
||||||
public class CompositeIterator<T> implements Iterator<T> { |
|
||||||
|
|
||||||
private Iterator<Iterator<T>> listIterator; |
|
||||||
private Iterator<T> currentIterator; |
|
||||||
|
|
||||||
public CompositeIterator(Iterator<Iterator<T>> listIterator) { |
|
||||||
this.currentIterator = null; |
|
||||||
this.listIterator = listIterator; |
|
||||||
} |
|
||||||
|
|
||||||
@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(); |
|
||||||
} |
|
||||||
} |
|
@ -1,49 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,39 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,431 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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.SingleRoomBroadcastOperations; |
|
||||||
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.interceptor.EventInterceptor; |
|
||||||
import com.fr.third.socketio.listener.interceptor.Interceptor; |
|
||||||
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 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 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; |
|
||||||
private final Configuration configuration; |
|
||||||
|
|
||||||
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(); |
|
||||||
this.configuration = configuration; |
|
||||||
} |
|
||||||
|
|
||||||
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; |
|
||||||
} |
|
||||||
for (EventInterceptor eventInterceptor: configuration.getEventInterceptors()) { |
|
||||||
try { |
|
||||||
//不影响后续执行
|
|
||||||
if (eventInterceptor.accept(eventName, client, args)) { |
|
||||||
eventInterceptor.before(client, args, ackRequest); |
|
||||||
} |
|
||||||
} catch (Exception e) { |
|
||||||
exceptionListener.onEventException(e, args, client); |
|
||||||
} |
|
||||||
} |
|
||||||
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; |
|
||||||
} |
|
||||||
} finally { |
|
||||||
for (EventInterceptor eventInterceptor: configuration.getEventInterceptors()) { |
|
||||||
try { |
|
||||||
//不影响后续执行
|
|
||||||
if (eventInterceptor.accept(eventName, client, args)) { |
|
||||||
eventInterceptor.after(client, args, ackRequest); |
|
||||||
} |
|
||||||
} catch (Exception e) { |
|
||||||
exceptionListener.onEventException(e, args, client); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
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()); |
|
||||||
|
|
||||||
// client must leave all rooms and publish the leave msg one by one on disconnect.
|
|
||||||
for (String joinedRoom : joinedRooms) { |
|
||||||
leave(roomClients, joinedRoom, client.getSessionId()); |
|
||||||
storeFactory.pubSubStore().publish(PubSubType.LEAVE, new JoinLeaveMessage(client.getSessionId(), joinedRoom, getName())); |
|
||||||
} |
|
||||||
clientRooms.remove(client.getSessionId()); |
|
||||||
|
|
||||||
callInterceptor(client, false, true); |
|
||||||
try { |
|
||||||
for (DisconnectListener listener : disconnectListeners) { |
|
||||||
listener.onDisconnect(client); |
|
||||||
} |
|
||||||
} catch (Exception e) { |
|
||||||
exceptionListener.onDisconnectException(e, client); |
|
||||||
} finally { |
|
||||||
callInterceptor(client, false, false); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@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())); |
|
||||||
|
|
||||||
callInterceptor(client, true, true); |
|
||||||
try { |
|
||||||
for (ConnectListener listener : connectListeners) { |
|
||||||
listener.onConnect(client); |
|
||||||
} |
|
||||||
} catch (Exception e) { |
|
||||||
exceptionListener.onConnectException(e, client); |
|
||||||
} finally { |
|
||||||
callInterceptor(client, true, false); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private void callInterceptor(SocketIOClient client, boolean connect, boolean before) { |
|
||||||
List<Interceptor> interceptors = connect ? configuration.getConnectInterceptors() : configuration.getDisconnectInterceptors(); |
|
||||||
for (Interceptor interceptor: interceptors) { |
|
||||||
try{ |
|
||||||
if (interceptor.accept(client)) { |
|
||||||
if (before) { |
|
||||||
interceptor.before(client); |
|
||||||
} else { |
|
||||||
interceptor.after(client); |
|
||||||
} |
|
||||||
} |
|
||||||
} catch (Exception e) { |
|
||||||
if (connect) { |
|
||||||
exceptionListener.onConnectException(e, client); |
|
||||||
} else { |
|
||||||
exceptionListener.onDisconnectException(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 SingleRoomBroadcastOperations(getName(), getName(), allClients.values(), storeFactory); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public BroadcastOperations getRoomOperations(String room) { |
|
||||||
return new SingleRoomBroadcastOperations(getName(), room, 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); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,75 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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 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.Configuration; |
|
||||||
import com.fr.third.socketio.SocketIOClient; |
|
||||||
import com.fr.third.socketio.SocketIONamespace; |
|
||||||
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(); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,33 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,52 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,42 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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 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; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,366 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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.io.IOException; |
|
||||||
import java.io.InputStream; |
|
||||||
import java.io.OutputStream; |
|
||||||
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 java.util.TimeZone; |
|
||||||
|
|
||||||
import com.fr.third.slf4j.Logger; |
|
||||||
import com.fr.third.slf4j.LoggerFactory; |
|
||||||
|
|
||||||
import com.fr.third.socketio.AckCallback; |
|
||||||
import com.fr.third.socketio.MultiTypeAckCallback; |
|
||||||
import com.fr.third.socketio.namespace.Namespace; |
|
||||||
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; |
|
||||||
|
|
||||||
import io.netty.buffer.ByteBufInputStream; |
|
||||||
import io.netty.buffer.ByteBufOutputStream; |
|
||||||
import io.netty.util.internal.PlatformDependent; |
|
||||||
|
|
||||||
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); |
|
||||||
|
|
||||||
//时区设置,防止含有Data类型变量时序列化后,前台显示的data内时间不准确
|
|
||||||
objectMapper.setTimeZone(TimeZone.getDefault()); |
|
||||||
} |
|
||||||
|
|
||||||
@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((InputStream)src, valueType); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public AckArgs readAckArgs(ByteBufInputStream src, AckCallback<?> callback) throws IOException { |
|
||||||
currentAckClass.set(callback); |
|
||||||
return objectMapper.readValue((InputStream)src, AckArgs.class); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void writeValue(ByteBufOutputStream out, Object value) throws IOException { |
|
||||||
modifier.getSerializer().clear(); |
|
||||||
objectMapper.writeValue((OutputStream)out, value); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public List<byte[]> getArrays() { |
|
||||||
return modifier.getSerializer().getArrays(); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,46 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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.ByteBufInputStream; |
|
||||||
import io.netty.buffer.ByteBufOutputStream; |
|
||||||
|
|
||||||
import java.io.IOException; |
|
||||||
import java.util.List; |
|
||||||
|
|
||||||
import com.fr.third.socketio.AckCallback; |
|
||||||
|
|
||||||
/** |
|
||||||
* 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(); |
|
||||||
|
|
||||||
} |
|
@ -1,164 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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; |
|
||||||
|
|
||||||
import java.io.Serializable; |
|
||||||
import java.util.ArrayList; |
|
||||||
import java.util.Collections; |
|
||||||
import java.util.List; |
|
||||||
|
|
||||||
import com.fr.third.socketio.namespace.Namespace; |
|
||||||
|
|
||||||
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; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Creates a copy of #{@link Packet} with new namespace set |
|
||||||
* if it differs from current namespace. |
|
||||||
* Otherwise, returns original object unchanged |
|
||||||
* |
|
||||||
* @param namespace |
|
||||||
* @return packet |
|
||||||
*/ |
|
||||||
public Packet withNsp(String namespace) { |
|
||||||
if (this.nsp.equalsIgnoreCase(namespace)) { |
|
||||||
return this; |
|
||||||
} else { |
|
||||||
Packet newPacket = new Packet(this.type); |
|
||||||
newPacket.setAckId(this.ackId); |
|
||||||
newPacket.setData(this.data); |
|
||||||
newPacket.setDataSource(this.dataSource); |
|
||||||
newPacket.setName(this.name); |
|
||||||
newPacket.setSubType(this.subType); |
|
||||||
newPacket.setNsp(namespace); |
|
||||||
newPacket.attachments = this.attachments; |
|
||||||
newPacket.attachmentsCount = this.attachmentsCount; |
|
||||||
return newPacket; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
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 + "]"; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,326 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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.ByteBufUtil; |
|
||||||
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); |
|
||||||
} |
|
||||||
|
|
||||||
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(readNamespace(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()); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private String readNamespace(ByteBuf frame) { |
|
||||||
|
|
||||||
/** |
|
||||||
* namespace post request with url queryString, like |
|
||||||
* /message?a=1, |
|
||||||
* /message, |
|
||||||
*/ |
|
||||||
ByteBuf buffer = frame.slice(); |
|
||||||
// skip this frame
|
|
||||||
frame.readerIndex(frame.readerIndex() + frame.readableBytes()); |
|
||||||
|
|
||||||
int endIndex = buffer.bytesBefore((byte) '?'); |
|
||||||
if (endIndex > 0) { |
|
||||||
return readString(buffer, endIndex); |
|
||||||
} |
|
||||||
endIndex = buffer.bytesBefore((byte) ','); |
|
||||||
if (endIndex > 0) { |
|
||||||
return readString(buffer, endIndex); |
|
||||||
} |
|
||||||
return readString(buffer); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,353 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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; |
|
||||||
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; |
|
||||||
|
|
||||||
import com.fr.third.socketio.Configuration; |
|
||||||
|
|
||||||
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; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,60 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,102 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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 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(); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,36 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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(); |
|
||||||
|
|
||||||
} |
|
@ -1,112 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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(); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,133 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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(); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,60 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,41 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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() { |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,46 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,54 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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; |
|
||||||
import java.util.UUID; |
|
||||||
|
|
||||||
import com.fr.third.socketio.store.pubsub.BaseStoreFactory; |
|
||||||
import com.fr.third.socketio.store.pubsub.PubSubStore; |
|
||||||
|
|
||||||
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(); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,91 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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.org.redisson.api.RTopic; |
|
||||||
import com.fr.third.org.redisson.api.RedissonClient; |
|
||||||
import com.fr.third.org.redisson.api.listener.MessageListener; |
|
||||||
|
|
||||||
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 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 topic = redissonSub.getTopic(name); |
|
||||||
for (Integer id : regIds) { |
|
||||||
topic.removeListener(id); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void shutdown() { |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,51 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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.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); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,77 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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.org.redisson.Redisson; |
|
||||||
import com.fr.third.org.redisson.api.RedissonClient; |
|
||||||
|
|
||||||
import com.fr.third.socketio.store.pubsub.BaseStoreFactory; |
|
||||||
import com.fr.third.socketio.store.pubsub.PubSubStore; |
|
||||||
|
|
||||||
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); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,29 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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); |
|
||||||
|
|
||||||
} |
|
@ -1,44 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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(); |
|
||||||
|
|
||||||
} |
|
@ -1,97 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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.slf4j.Logger; |
|
||||||
import com.fr.third.slf4j.LoggerFactory; |
|
||||||
|
|
||||||
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 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)"; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,38 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,38 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,49 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,50 +0,0 @@ |
|||||||
/** |
|
||||||
* Copyright (c) 2012-2019 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; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue