richie
7 years ago
105 changed files with 8989 additions and 41 deletions
Binary file not shown.
@ -0,0 +1,91 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio; |
||||
|
||||
|
||||
/** |
||||
* Base ack callback class. |
||||
* |
||||
* Notifies about acknowledgement received from client |
||||
* via {@link #onSuccess} callback method. |
||||
* |
||||
* By default it may wait acknowledgement from client |
||||
* while {@link SocketIOClient} is alive. Timeout can be |
||||
* defined {@link #timeout} as constructor argument. |
||||
* |
||||
* This object is NOT actual anymore if {@link #onSuccess} or |
||||
* {@link #onTimeout} was executed. |
||||
* |
||||
* @param <T> - any serializable type |
||||
* |
||||
* @see VoidAckCallback |
||||
* @see MultiTypeAckCallback |
||||
* |
||||
*/ |
||||
public abstract class AckCallback<T> { |
||||
|
||||
protected final Class<T> resultClass; |
||||
protected final int timeout; |
||||
|
||||
/** |
||||
* Create AckCallback |
||||
* |
||||
* @param resultClass - result class
|
||||
*/ |
||||
public AckCallback(Class<T> resultClass) { |
||||
this(resultClass, -1); |
||||
} |
||||
|
||||
/** |
||||
* Creates AckCallback with timeout |
||||
* |
||||
* @param resultClass - result class
|
||||
* @param timeout - callback timeout in seconds |
||||
*/ |
||||
public AckCallback(Class<T> resultClass, int timeout) { |
||||
this.resultClass = resultClass; |
||||
this.timeout = timeout; |
||||
} |
||||
|
||||
public int getTimeout() { |
||||
return timeout; |
||||
} |
||||
|
||||
/** |
||||
* Executes only once when acknowledgement received from client. |
||||
* |
||||
* @param result - object sended by client |
||||
*/ |
||||
public abstract void onSuccess(T result); |
||||
|
||||
/** |
||||
* Invoked only once then <code>timeout</code> defined |
||||
* |
||||
*/ |
||||
public void onTimeout() { |
||||
|
||||
} |
||||
|
||||
/** |
||||
* Returns class of argument in {@link #onSuccess} method |
||||
* |
||||
* @return - result class
|
||||
*/ |
||||
public Class<T> getResultClass() { |
||||
return resultClass; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,39 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio; |
||||
|
||||
public enum AckMode { |
||||
|
||||
/** |
||||
* Send ack-response automatically on each ack-request |
||||
* <b>skip</b> exceptions during packet handling |
||||
*/ |
||||
AUTO, |
||||
|
||||
/** |
||||
* Send ack-response automatically on each ack-request |
||||
* only after <b>success</b> packet handling |
||||
*/ |
||||
AUTO_SUCCESS_ONLY, |
||||
|
||||
/** |
||||
* Turn off auto ack-response sending. |
||||
* Use AckRequest.sendAckData to send ack-response each time. |
||||
* |
||||
*/ |
||||
MANUAL |
||||
|
||||
} |
@ -0,0 +1,90 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio; |
||||
|
||||
import java.util.Arrays; |
||||
import java.util.List; |
||||
import java.util.concurrent.atomic.AtomicBoolean; |
||||
|
||||
import com.fr.third.socketio.listener.DataListener; |
||||
import com.fr.third.socketio.protocol.Packet; |
||||
import com.fr.third.socketio.protocol.PacketType; |
||||
|
||||
/** |
||||
* Ack request received from Socket.IO client. |
||||
* You can always check is it <code>true</code> through |
||||
* {@link #isAckRequested()} method. |
||||
* |
||||
* You can call {@link #sendAckData} methods only during |
||||
* {@link DataListener#onData} invocation. If {@link #sendAckData} |
||||
* not called it will be invoked with empty arguments right after |
||||
* {@link DataListener#onData} method execution by server. |
||||
* |
||||
* This object is NOT actual anymore if {@link #sendAckData} was |
||||
* executed or {@link DataListener#onData} invocation finished. |
||||
* |
||||
*/ |
||||
public class AckRequest { |
||||
|
||||
private final Packet originalPacket; |
||||
private final SocketIOClient client; |
||||
private final AtomicBoolean sended = new AtomicBoolean(); |
||||
|
||||
public AckRequest(Packet originalPacket, SocketIOClient client) { |
||||
this.originalPacket = originalPacket; |
||||
this.client = client; |
||||
} |
||||
|
||||
/** |
||||
* Check whether ack request was made |
||||
* |
||||
* @return true if ack requested by client |
||||
*/ |
||||
public boolean isAckRequested() { |
||||
return originalPacket.isAckRequested(); |
||||
} |
||||
|
||||
/** |
||||
* Send ack data to client. |
||||
* Can be invoked only once during {@link DataListener#onData} |
||||
* method invocation. |
||||
* |
||||
* @param objs - ack data objects |
||||
*/ |
||||
public void sendAckData(Object ... objs) { |
||||
List<Object> args = Arrays.asList(objs); |
||||
sendAckData(args); |
||||
} |
||||
|
||||
/** |
||||
* Send ack data to client. |
||||
* Can be invoked only once during {@link DataListener#onData} |
||||
* method invocation. |
||||
* |
||||
* @param objs - ack data object list |
||||
*/ |
||||
public void sendAckData(List<Object> objs) { |
||||
if (!isAckRequested() || !sended.compareAndSet(false, true)) { |
||||
return; |
||||
} |
||||
Packet ackPacket = new Packet(PacketType.MESSAGE); |
||||
ackPacket.setSubType(PacketType.ACK); |
||||
ackPacket.setAckId(originalPacket.getAckId()); |
||||
ackPacket.setData(objs); |
||||
client.send(ackPacket); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,28 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio; |
||||
|
||||
public interface AuthorizationListener { |
||||
|
||||
/** |
||||
* Checks is client with handshake data is authorized |
||||
* |
||||
* @param data - handshake data |
||||
* @return - <b>true</b> if client is authorized of <b>false</b> otherwise |
||||
*/ |
||||
boolean isAuthorized(HandshakeData data); |
||||
|
||||
} |
@ -0,0 +1,82 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio; |
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean; |
||||
import java.util.concurrent.atomic.AtomicInteger; |
||||
|
||||
public class BroadcastAckCallback<T> { |
||||
|
||||
final AtomicBoolean loopFinished = new AtomicBoolean(); |
||||
final AtomicInteger counter = new AtomicInteger(); |
||||
final AtomicBoolean successExecuted = new AtomicBoolean(); |
||||
final Class<T> resultClass; |
||||
final int timeout; |
||||
|
||||
public BroadcastAckCallback(Class<T> resultClass, int timeout) { |
||||
this.resultClass = resultClass; |
||||
this.timeout = timeout; |
||||
} |
||||
|
||||
public BroadcastAckCallback(Class<T> resultClass) { |
||||
this(resultClass, -1); |
||||
} |
||||
|
||||
final AckCallback<T> createClientCallback(final SocketIOClient client) { |
||||
counter.getAndIncrement(); |
||||
return new AckCallback<T>(resultClass, timeout) { |
||||
@Override |
||||
public void onSuccess(T result) { |
||||
counter.getAndDecrement(); |
||||
onClientSuccess(client, result); |
||||
executeSuccess(); |
||||
} |
||||
|
||||
@Override |
||||
public void onTimeout() { |
||||
onClientTimeout(client); |
||||
} |
||||
|
||||
}; |
||||
} |
||||
|
||||
protected void onClientTimeout(SocketIOClient client) { |
||||
|
||||
} |
||||
|
||||
protected void onClientSuccess(SocketIOClient client, T result) { |
||||
|
||||
} |
||||
|
||||
protected void onAllSuccess() { |
||||
|
||||
} |
||||
|
||||
private void executeSuccess() { |
||||
if (loopFinished.get() |
||||
&& counter.get() == 0 |
||||
&& successExecuted.compareAndSet(false, true)) { |
||||
onAllSuccess(); |
||||
} |
||||
} |
||||
|
||||
void loopFinished() { |
||||
loopFinished.set(true); |
||||
executeSuccess(); |
||||
} |
||||
|
||||
} |
||||
|
@ -0,0 +1,137 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio; |
||||
|
||||
import java.util.Arrays; |
||||
import java.util.Collection; |
||||
import java.util.HashMap; |
||||
import java.util.HashSet; |
||||
import java.util.Map; |
||||
import java.util.Map.Entry; |
||||
import java.util.Set; |
||||
|
||||
import com.fr.third.socketio.misc.IterableCollection; |
||||
import com.fr.third.socketio.namespace.Namespace; |
||||
import com.fr.third.socketio.protocol.Packet; |
||||
import com.fr.third.socketio.protocol.PacketType; |
||||
import com.fr.third.socketio.store.StoreFactory; |
||||
import com.fr.third.socketio.store.pubsub.DispatchMessage; |
||||
import com.fr.third.socketio.store.pubsub.PubSubType; |
||||
|
||||
/** |
||||
* Fully thread-safe. |
||||
* |
||||
*/ |
||||
public class BroadcastOperations implements ClientOperations { |
||||
|
||||
private final Iterable<SocketIOClient> clients; |
||||
private final StoreFactory storeFactory; |
||||
|
||||
public BroadcastOperations(Iterable<SocketIOClient> clients, StoreFactory storeFactory) { |
||||
super(); |
||||
this.clients = clients; |
||||
this.storeFactory = storeFactory; |
||||
} |
||||
|
||||
private void dispatch(Packet packet) { |
||||
Map<String, Set<String>> namespaceRooms = new HashMap<String, Set<String>>(); |
||||
for (SocketIOClient socketIOClient : clients) { |
||||
Namespace namespace = (Namespace)socketIOClient.getNamespace(); |
||||
Set<String> rooms = namespace.getRooms(socketIOClient); |
||||
|
||||
Set<String> roomsList = namespaceRooms.get(namespace.getName()); |
||||
if (roomsList == null) { |
||||
roomsList = new HashSet<String>(); |
||||
namespaceRooms.put(namespace.getName(), roomsList); |
||||
} |
||||
roomsList.addAll(rooms); |
||||
} |
||||
for (Entry<String, Set<String>> entry : namespaceRooms.entrySet()) { |
||||
for (String room : entry.getValue()) { |
||||
storeFactory.pubSubStore().publish(PubSubType.DISPATCH, new DispatchMessage(room, packet, entry.getKey())); |
||||
} |
||||
} |
||||
} |
||||
|
||||
public Collection<SocketIOClient> getClients() { |
||||
return new IterableCollection<SocketIOClient>(clients); |
||||
} |
||||
|
||||
@Override |
||||
public void send(Packet packet) { |
||||
for (SocketIOClient client : clients) { |
||||
client.send(packet); |
||||
} |
||||
dispatch(packet); |
||||
} |
||||
|
||||
public <T> void send(Packet packet, BroadcastAckCallback<T> ackCallback) { |
||||
for (SocketIOClient client : clients) { |
||||
client.send(packet, ackCallback.createClientCallback(client)); |
||||
} |
||||
ackCallback.loopFinished(); |
||||
} |
||||
|
||||
@Override |
||||
public void disconnect() { |
||||
for (SocketIOClient client : clients) { |
||||
client.disconnect(); |
||||
} |
||||
} |
||||
|
||||
public void sendEvent(String name, SocketIOClient excludedClient, Object... data) { |
||||
Packet packet = new Packet(PacketType.MESSAGE); |
||||
packet.setSubType(PacketType.EVENT); |
||||
packet.setName(name); |
||||
packet.setData(Arrays.asList(data)); |
||||
|
||||
for (SocketIOClient client : clients) { |
||||
if (client.getSessionId().equals(excludedClient.getSessionId())) { |
||||
continue; |
||||
} |
||||
client.send(packet); |
||||
} |
||||
dispatch(packet); |
||||
} |
||||
|
||||
@Override |
||||
public void sendEvent(String name, Object... data) { |
||||
Packet packet = new Packet(PacketType.MESSAGE); |
||||
packet.setSubType(PacketType.EVENT); |
||||
packet.setName(name); |
||||
packet.setData(Arrays.asList(data)); |
||||
send(packet); |
||||
} |
||||
|
||||
public <T> void sendEvent(String name, Object data, BroadcastAckCallback<T> ackCallback) { |
||||
for (SocketIOClient client : clients) { |
||||
client.sendEvent(name, ackCallback.createClientCallback(client), data); |
||||
} |
||||
ackCallback.loopFinished(); |
||||
} |
||||
|
||||
public <T> void sendEvent(String name, Object data, SocketIOClient excludedClient, BroadcastAckCallback<T> ackCallback) { |
||||
for (SocketIOClient client : clients) { |
||||
if (client.getSessionId().equals(excludedClient.getSessionId())) { |
||||
continue; |
||||
} |
||||
client.sendEvent(name, ackCallback.createClientCallback(client), data); |
||||
} |
||||
ackCallback.loopFinished(); |
||||
} |
||||
|
||||
|
||||
} |
@ -0,0 +1,49 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio; |
||||
|
||||
import com.fr.third.socketio.protocol.Packet; |
||||
|
||||
/** |
||||
* Available client operations |
||||
* |
||||
*/ |
||||
public interface ClientOperations { |
||||
|
||||
/** |
||||
* Send custom packet. |
||||
* But {@link ClientOperations#sendEvent} method |
||||
* usage is enough for most cases. |
||||
* |
||||
* @param packet - packet to send |
||||
*/ |
||||
void send(Packet packet); |
||||
|
||||
/** |
||||
* Disconnect client |
||||
* |
||||
*/ |
||||
void disconnect(); |
||||
|
||||
/** |
||||
* Send event |
||||
* |
||||
* @param name - event name |
||||
* @param data - event data |
||||
*/ |
||||
void sendEvent(String name, Object ... data); |
||||
|
||||
} |
@ -0,0 +1,574 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio; |
||||
|
||||
import com.fr.third.socketio.handler.SuccessAuthorizationListener; |
||||
import com.fr.third.socketio.listener.DefaultExceptionListener; |
||||
import com.fr.third.socketio.listener.ExceptionListener; |
||||
import com.fr.third.socketio.protocol.JsonSupport; |
||||
import com.fr.third.socketio.store.MemoryStoreFactory; |
||||
import com.fr.third.socketio.store.StoreFactory; |
||||
|
||||
import javax.net.ssl.KeyManagerFactory; |
||||
import java.io.InputStream; |
||||
import java.util.Arrays; |
||||
import java.util.List; |
||||
|
||||
public class Configuration { |
||||
|
||||
private ExceptionListener exceptionListener = new DefaultExceptionListener(); |
||||
|
||||
private String context = "/socket.io"; |
||||
|
||||
private List<Transport> transports = Arrays.asList(Transport.WEBSOCKET, Transport.POLLING); |
||||
|
||||
private int bossThreads = 0; // 0 = current_processors_amount * 2
|
||||
private int workerThreads = 0; // 0 = current_processors_amount * 2
|
||||
private boolean useLinuxNativeEpoll; |
||||
|
||||
private boolean allowCustomRequests = false; |
||||
|
||||
private int upgradeTimeout = 10000; |
||||
private int pingTimeout = 60000; |
||||
private int pingInterval = 25000; |
||||
private int firstDataTimeout = 5000; |
||||
|
||||
private int maxHttpContentLength = 64 * 1024; |
||||
private int maxFramePayloadLength = 64 * 1024; |
||||
|
||||
private String packagePrefix; |
||||
private String hostname; |
||||
private int port = -1; |
||||
|
||||
private String sslProtocol = "TLSv1"; |
||||
|
||||
private String keyStoreFormat = "JKS"; |
||||
private InputStream keyStore; |
||||
private String keyStorePassword; |
||||
|
||||
private String trustStoreFormat = "JKS"; |
||||
private InputStream trustStore; |
||||
private String trustStorePassword; |
||||
|
||||
private String keyManagerFactoryAlgorithm = KeyManagerFactory.getDefaultAlgorithm(); |
||||
|
||||
private boolean preferDirectBuffer = true; |
||||
|
||||
private SocketConfig socketConfig = new SocketConfig(); |
||||
|
||||
private StoreFactory storeFactory = new MemoryStoreFactory(); |
||||
|
||||
private JsonSupport jsonSupport; |
||||
|
||||
private AuthorizationListener authorizationListener = new SuccessAuthorizationListener(); |
||||
|
||||
private AckMode ackMode = AckMode.AUTO_SUCCESS_ONLY; |
||||
|
||||
private boolean addVersionHeader = true; |
||||
|
||||
private String origin; |
||||
|
||||
private boolean httpCompression = true; |
||||
|
||||
private boolean websocketCompression = true; |
||||
|
||||
public Configuration() { |
||||
} |
||||
|
||||
/** |
||||
* Defend from further modifications by cloning |
||||
* |
||||
* @param conf - Configuration object to clone |
||||
*/ |
||||
Configuration(Configuration conf) { |
||||
setBossThreads(conf.getBossThreads()); |
||||
setWorkerThreads(conf.getWorkerThreads()); |
||||
setUseLinuxNativeEpoll(conf.isUseLinuxNativeEpoll()); |
||||
|
||||
setPingInterval(conf.getPingInterval()); |
||||
setPingTimeout(conf.getPingTimeout()); |
||||
|
||||
setHostname(conf.getHostname()); |
||||
setPort(conf.getPort()); |
||||
|
||||
if (conf.getJsonSupport() == null) { |
||||
try { |
||||
getClass().getClassLoader().loadClass("com.fasterxml.jackson.databind.ObjectMapper"); |
||||
try { |
||||
Class<?> jjs = getClass().getClassLoader().loadClass("com.fr.third.socketio.protocol.JacksonJsonSupport"); |
||||
JsonSupport js = (JsonSupport) jjs.getConstructor().newInstance(); |
||||
conf.setJsonSupport(js); |
||||
} catch (Exception e) { |
||||
throw new IllegalArgumentException(e); |
||||
} |
||||
} catch (ClassNotFoundException e) { |
||||
throw new IllegalArgumentException("Can't find jackson lib in classpath", e); |
||||
} |
||||
} |
||||
|
||||
setJsonSupport(new JsonSupportWrapper(conf.getJsonSupport())); |
||||
setContext(conf.getContext()); |
||||
setAllowCustomRequests(conf.isAllowCustomRequests()); |
||||
|
||||
setKeyStorePassword(conf.getKeyStorePassword()); |
||||
setKeyStore(conf.getKeyStore()); |
||||
setKeyStoreFormat(conf.getKeyStoreFormat()); |
||||
setTrustStore(conf.getTrustStore()); |
||||
setTrustStoreFormat(conf.getTrustStoreFormat()); |
||||
setTrustStorePassword(conf.getTrustStorePassword()); |
||||
setKeyManagerFactoryAlgorithm(conf.getKeyManagerFactoryAlgorithm()); |
||||
|
||||
setTransports(conf.getTransports().toArray(new Transport[conf.getTransports().size()])); |
||||
setMaxHttpContentLength(conf.getMaxHttpContentLength()); |
||||
setPackagePrefix(conf.getPackagePrefix()); |
||||
|
||||
setPreferDirectBuffer(conf.isPreferDirectBuffer()); |
||||
setStoreFactory(conf.getStoreFactory()); |
||||
setAuthorizationListener(conf.getAuthorizationListener()); |
||||
setExceptionListener(conf.getExceptionListener()); |
||||
setSocketConfig(conf.getSocketConfig()); |
||||
setAckMode(conf.getAckMode()); |
||||
setMaxFramePayloadLength(conf.getMaxFramePayloadLength()); |
||||
setUpgradeTimeout(conf.getUpgradeTimeout()); |
||||
|
||||
setAddVersionHeader(conf.isAddVersionHeader()); |
||||
setOrigin(conf.getOrigin()); |
||||
setSSLProtocol(conf.getSSLProtocol()); |
||||
|
||||
setHttpCompression(conf.isHttpCompression()); |
||||
setWebsocketCompression(conf.isWebsocketCompression()); |
||||
} |
||||
|
||||
public JsonSupport getJsonSupport() { |
||||
return jsonSupport; |
||||
} |
||||
|
||||
/** |
||||
* Allows to setup custom implementation of |
||||
* JSON serialization/deserialization |
||||
* |
||||
* @param jsonSupport - json mapper |
||||
* |
||||
* @see JsonSupport |
||||
*/ |
||||
public void setJsonSupport(JsonSupport jsonSupport) { |
||||
this.jsonSupport = jsonSupport; |
||||
} |
||||
|
||||
public String getHostname() { |
||||
return hostname; |
||||
} |
||||
|
||||
/** |
||||
* Optional parameter. If not set then bind address |
||||
* will be 0.0.0.0 or ::0 |
||||
* |
||||
* @param hostname - name of host |
||||
*/ |
||||
public void setHostname(String hostname) { |
||||
this.hostname = hostname; |
||||
} |
||||
|
||||
public int getPort() { |
||||
return port; |
||||
} |
||||
public void setPort(int port) { |
||||
this.port = port; |
||||
} |
||||
|
||||
public int getBossThreads() { |
||||
return bossThreads; |
||||
} |
||||
public void setBossThreads(int bossThreads) { |
||||
this.bossThreads = bossThreads; |
||||
} |
||||
|
||||
public int getWorkerThreads() { |
||||
return workerThreads; |
||||
} |
||||
public void setWorkerThreads(int workerThreads) { |
||||
this.workerThreads = workerThreads; |
||||
} |
||||
|
||||
/** |
||||
* Ping interval |
||||
* |
||||
* @param heartbeatIntervalSecs - time in milliseconds |
||||
*/ |
||||
public void setPingInterval(int heartbeatIntervalSecs) { |
||||
this.pingInterval = heartbeatIntervalSecs; |
||||
} |
||||
public int getPingInterval() { |
||||
return pingInterval; |
||||
} |
||||
|
||||
/** |
||||
* Ping timeout |
||||
* Use <code>0</code> to disable it |
||||
* |
||||
* @param heartbeatTimeoutSecs - time in milliseconds |
||||
*/ |
||||
public void setPingTimeout(int heartbeatTimeoutSecs) { |
||||
this.pingTimeout = heartbeatTimeoutSecs; |
||||
} |
||||
public int getPingTimeout() { |
||||
return pingTimeout; |
||||
} |
||||
public boolean isHeartbeatsEnabled() { |
||||
return pingTimeout > 0; |
||||
} |
||||
|
||||
public String getContext() { |
||||
return context; |
||||
} |
||||
public void setContext(String context) { |
||||
this.context = context; |
||||
} |
||||
|
||||
public boolean isAllowCustomRequests() { |
||||
return allowCustomRequests; |
||||
} |
||||
|
||||
/** |
||||
* Allow to service custom requests differs from socket.io protocol. |
||||
* In this case it's necessary to add own handler which handle them |
||||
* to avoid hang connections. |
||||
* Default is {@code false} |
||||
* |
||||
* @param allowCustomRequests - {@code true} to allow |
||||
*/ |
||||
public void setAllowCustomRequests(boolean allowCustomRequests) { |
||||
this.allowCustomRequests = allowCustomRequests; |
||||
} |
||||
|
||||
/** |
||||
* SSL key store password |
||||
* |
||||
* @param keyStorePassword - password of key store |
||||
*/ |
||||
public void setKeyStorePassword(String keyStorePassword) { |
||||
this.keyStorePassword = keyStorePassword; |
||||
} |
||||
public String getKeyStorePassword() { |
||||
return keyStorePassword; |
||||
} |
||||
|
||||
/** |
||||
* SSL key store stream, maybe appointed to any source |
||||
* |
||||
* @param keyStore - key store input stream |
||||
*/ |
||||
public void setKeyStore(InputStream keyStore) { |
||||
this.keyStore = keyStore; |
||||
} |
||||
public InputStream getKeyStore() { |
||||
return keyStore; |
||||
} |
||||
|
||||
/** |
||||
* Key store format |
||||
* |
||||
* @param keyStoreFormat - key store format |
||||
*/ |
||||
public void setKeyStoreFormat(String keyStoreFormat) { |
||||
this.keyStoreFormat = keyStoreFormat; |
||||
} |
||||
public String getKeyStoreFormat() { |
||||
return keyStoreFormat; |
||||
} |
||||
|
||||
/** |
||||
* Set maximum http content length limit |
||||
* |
||||
* @param value |
||||
* the maximum length of the aggregated http content. |
||||
*/ |
||||
public void setMaxHttpContentLength(int value) { |
||||
this.maxHttpContentLength = value; |
||||
} |
||||
public int getMaxHttpContentLength() { |
||||
return maxHttpContentLength; |
||||
} |
||||
|
||||
/** |
||||
* Transports supported by server |
||||
* |
||||
* @param transports - list of transports |
||||
*/ |
||||
public void setTransports(Transport ... transports) { |
||||
if (transports.length == 0) { |
||||
throw new IllegalArgumentException("Transports list can't be empty"); |
||||
} |
||||
this.transports = Arrays.asList(transports); |
||||
} |
||||
public List<Transport> getTransports() { |
||||
return transports; |
||||
} |
||||
|
||||
/** |
||||
* Package prefix for sending json-object from client |
||||
* without full class name. |
||||
* |
||||
* With defined package prefix socket.io client |
||||
* just need to define '@class: 'SomeType'' in json object |
||||
* instead of '@class: 'com.full.package.name.SomeType'' |
||||
* |
||||
* @param packagePrefix - prefix string |
||||
* |
||||
*/ |
||||
public void setPackagePrefix(String packagePrefix) { |
||||
this.packagePrefix = packagePrefix; |
||||
} |
||||
public String getPackagePrefix() { |
||||
return packagePrefix; |
||||
} |
||||
|
||||
/** |
||||
* Buffer allocation method used during packet encoding. |
||||
* Default is {@code true} |
||||
* |
||||
* @param preferDirectBuffer {@code true} if a direct buffer should be tried to be used as target for |
||||
* the encoded messages. If {@code false} is used it will allocate a heap |
||||
* buffer, which is backed by an byte array. |
||||
*/ |
||||
public void setPreferDirectBuffer(boolean preferDirectBuffer) { |
||||
this.preferDirectBuffer = preferDirectBuffer; |
||||
} |
||||
public boolean isPreferDirectBuffer() { |
||||
return preferDirectBuffer; |
||||
} |
||||
|
||||
/** |
||||
* Data store - used to store session data and implements distributed pubsub. |
||||
* Default is {@code MemoryStoreFactory} |
||||
* |
||||
* @param clientStoreFactory - implements StoreFactory |
||||
* |
||||
* @see MemoryStoreFactory |
||||
*/ |
||||
public void setStoreFactory(StoreFactory clientStoreFactory) { |
||||
this.storeFactory = clientStoreFactory; |
||||
} |
||||
public StoreFactory getStoreFactory() { |
||||
return storeFactory; |
||||
} |
||||
|
||||
/** |
||||
* Authorization listener invoked on every handshake. |
||||
* Accepts or denies a client by {@code AuthorizationListener.isAuthorized} method. |
||||
* <b>Accepts</b> all clients by default. |
||||
* |
||||
* @param authorizationListener - authorization listener itself |
||||
* |
||||
* @see AuthorizationListener |
||||
*/ |
||||
public void setAuthorizationListener(AuthorizationListener authorizationListener) { |
||||
this.authorizationListener = authorizationListener; |
||||
} |
||||
public AuthorizationListener getAuthorizationListener() { |
||||
return authorizationListener; |
||||
} |
||||
|
||||
/** |
||||
* Exception listener invoked on any exception in |
||||
* SocketIO listener |
||||
* |
||||
* @param exceptionListener - listener |
||||
* |
||||
* @see ExceptionListener |
||||
*/ |
||||
public void setExceptionListener(ExceptionListener exceptionListener) { |
||||
this.exceptionListener = exceptionListener; |
||||
} |
||||
public ExceptionListener getExceptionListener() { |
||||
return exceptionListener; |
||||
} |
||||
|
||||
public SocketConfig getSocketConfig() { |
||||
return socketConfig; |
||||
} |
||||
/** |
||||
* TCP socket configuration |
||||
* |
||||
* @param socketConfig - config |
||||
*/ |
||||
public void setSocketConfig(SocketConfig socketConfig) { |
||||
this.socketConfig = socketConfig; |
||||
} |
||||
|
||||
/** |
||||
* Auto ack-response mode |
||||
* Default is {@code AckMode.AUTO_SUCCESS_ONLY} |
||||
* |
||||
* @see AckMode |
||||
* |
||||
* @param ackMode - ack mode |
||||
*/ |
||||
public void setAckMode(AckMode ackMode) { |
||||
this.ackMode = ackMode; |
||||
} |
||||
public AckMode getAckMode() { |
||||
return ackMode; |
||||
} |
||||
|
||||
|
||||
public String getTrustStoreFormat() { |
||||
return trustStoreFormat; |
||||
} |
||||
public void setTrustStoreFormat(String trustStoreFormat) { |
||||
this.trustStoreFormat = trustStoreFormat; |
||||
} |
||||
|
||||
public InputStream getTrustStore() { |
||||
return trustStore; |
||||
} |
||||
public void setTrustStore(InputStream trustStore) { |
||||
this.trustStore = trustStore; |
||||
} |
||||
|
||||
public String getTrustStorePassword() { |
||||
return trustStorePassword; |
||||
} |
||||
public void setTrustStorePassword(String trustStorePassword) { |
||||
this.trustStorePassword = trustStorePassword; |
||||
} |
||||
|
||||
public String getKeyManagerFactoryAlgorithm() { |
||||
return keyManagerFactoryAlgorithm; |
||||
} |
||||
public void setKeyManagerFactoryAlgorithm(String keyManagerFactoryAlgorithm) { |
||||
this.keyManagerFactoryAlgorithm = keyManagerFactoryAlgorithm; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Set maximum websocket frame content length limit |
||||
* |
||||
* @param maxFramePayloadLength - length |
||||
*/ |
||||
public void setMaxFramePayloadLength(int maxFramePayloadLength) { |
||||
this.maxFramePayloadLength = maxFramePayloadLength; |
||||
} |
||||
public int getMaxFramePayloadLength() { |
||||
return maxFramePayloadLength; |
||||
} |
||||
|
||||
/** |
||||
* Transport upgrade timeout in milliseconds |
||||
* |
||||
* @param upgradeTimeout - upgrade timeout |
||||
*/ |
||||
public void setUpgradeTimeout(int upgradeTimeout) { |
||||
this.upgradeTimeout = upgradeTimeout; |
||||
} |
||||
public int getUpgradeTimeout() { |
||||
return upgradeTimeout; |
||||
} |
||||
|
||||
/** |
||||
* Adds <b>Server</b> header with lib version to http response. |
||||
* <p> |
||||
* Default is <code>true</code> |
||||
* |
||||
* @param addVersionHeader - <code>true</code> to add header |
||||
*/ |
||||
public void setAddVersionHeader(boolean addVersionHeader) { |
||||
this.addVersionHeader = addVersionHeader; |
||||
} |
||||
public boolean isAddVersionHeader() { |
||||
return addVersionHeader; |
||||
} |
||||
|
||||
/** |
||||
* Set <b>Access-Control-Allow-Origin</b> header value for http each |
||||
* response. |
||||
* Default is <code>null</code> |
||||
* |
||||
* If value is <code>null</code> then request <b>ORIGIN</b> header value used. |
||||
* |
||||
* @param origin - origin |
||||
*/ |
||||
public void setOrigin(String origin) { |
||||
this.origin = origin; |
||||
} |
||||
public String getOrigin() { |
||||
return origin; |
||||
} |
||||
|
||||
public boolean isUseLinuxNativeEpoll() { |
||||
return useLinuxNativeEpoll; |
||||
} |
||||
public void setUseLinuxNativeEpoll(boolean useLinuxNativeEpoll) { |
||||
this.useLinuxNativeEpoll = useLinuxNativeEpoll; |
||||
} |
||||
|
||||
/** |
||||
* Set the name of the requested SSL protocol |
||||
* |
||||
* @param sslProtocol - name of protocol |
||||
*/ |
||||
public void setSSLProtocol(String sslProtocol) { |
||||
this.sslProtocol = sslProtocol; |
||||
} |
||||
public String getSSLProtocol() { |
||||
return sslProtocol; |
||||
} |
||||
|
||||
/** |
||||
* Timeout between channel opening and first data transfer |
||||
* Helps to avoid 'silent channel' attack and prevents |
||||
* 'Too many open files' problem in this case |
||||
* |
||||
* @param firstDataTimeout - timeout value |
||||
*/ |
||||
public void setFirstDataTimeout(int firstDataTimeout) { |
||||
this.firstDataTimeout = firstDataTimeout; |
||||
} |
||||
public int getFirstDataTimeout() { |
||||
return firstDataTimeout; |
||||
} |
||||
|
||||
/** |
||||
* Activate http protocol compression. Uses {@code gzip} or |
||||
* {@code deflate} encoding choice depends on the {@code "Accept-Encoding"} header value. |
||||
* <p> |
||||
* Default is <code>true</code> |
||||
* |
||||
* @param httpCompression - <code>true</code> to use http compression |
||||
*/ |
||||
public void setHttpCompression(boolean httpCompression) { |
||||
this.httpCompression = httpCompression; |
||||
} |
||||
public boolean isHttpCompression() { |
||||
return httpCompression; |
||||
} |
||||
|
||||
/** |
||||
* Activate websocket protocol compression. |
||||
* Uses {@code permessage-deflate} encoding only. |
||||
* <p> |
||||
* Default is <code>true</code> |
||||
* |
||||
* @param websocketCompression - <code>true</code> to use websocket compression |
||||
*/ |
||||
public void setWebsocketCompression(boolean websocketCompression) { |
||||
this.websocketCompression = websocketCompression; |
||||
} |
||||
public boolean isWebsocketCompression() { |
||||
return websocketCompression; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,26 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio; |
||||
|
||||
import com.fr.third.socketio.handler.ClientHead; |
||||
|
||||
|
||||
|
||||
public interface Disconnectable { |
||||
|
||||
void onDisconnect(ClientHead client); |
||||
|
||||
} |
@ -0,0 +1,20 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio; |
||||
|
||||
public interface DisconnectableHub extends Disconnectable { |
||||
|
||||
} |
@ -0,0 +1,122 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio; |
||||
|
||||
import java.io.Serializable; |
||||
import java.net.InetSocketAddress; |
||||
import java.util.Date; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
|
||||
import io.netty.handler.codec.http.HttpHeaders; |
||||
|
||||
public class HandshakeData implements Serializable { |
||||
|
||||
private static final long serialVersionUID = 1196350300161819978L; |
||||
|
||||
private HttpHeaders headers; |
||||
private InetSocketAddress address; |
||||
private Date time = new Date(); |
||||
private InetSocketAddress local; |
||||
private String url; |
||||
private Map<String, List<String>> urlParams; |
||||
private boolean xdomain; |
||||
|
||||
// needed for correct deserialization
|
||||
public HandshakeData() { |
||||
} |
||||
|
||||
public HandshakeData(HttpHeaders headers, Map<String, List<String>> urlParams, InetSocketAddress address, String url, boolean xdomain) { |
||||
this(headers, urlParams, address, null, url, xdomain); |
||||
} |
||||
|
||||
public HandshakeData(HttpHeaders headers, Map<String, List<String>> urlParams, InetSocketAddress address, InetSocketAddress local, String url, boolean xdomain) { |
||||
super(); |
||||
this.headers = headers; |
||||
this.urlParams = urlParams; |
||||
this.address = address; |
||||
this.local = local; |
||||
this.url = url; |
||||
this.xdomain = xdomain; |
||||
} |
||||
|
||||
/** |
||||
* Client network address |
||||
* |
||||
* @return network address |
||||
*/ |
||||
public InetSocketAddress getAddress() { |
||||
return address; |
||||
} |
||||
|
||||
/** |
||||
* Connection local address |
||||
* |
||||
* @return local address |
||||
*/ |
||||
public InetSocketAddress getLocal() { |
||||
return local; |
||||
} |
||||
|
||||
/** |
||||
* Http headers sent during first client request |
||||
* |
||||
* @return headers |
||||
*/ |
||||
public HttpHeaders getHttpHeaders() { |
||||
return headers; |
||||
} |
||||
|
||||
/** |
||||
* Client connection date |
||||
* |
||||
* @return date |
||||
*/ |
||||
public Date getTime() { |
||||
return time; |
||||
} |
||||
|
||||
/** |
||||
* Url used by client during first request |
||||
* |
||||
* @return url |
||||
*/ |
||||
public String getUrl() { |
||||
return url; |
||||
} |
||||
|
||||
public boolean isXdomain() { |
||||
return xdomain; |
||||
} |
||||
|
||||
/** |
||||
* Url params stored in url used by client during first request |
||||
* |
||||
* @return map |
||||
*/ |
||||
public Map<String, List<String>> getUrlParams() { |
||||
return urlParams; |
||||
} |
||||
|
||||
public String getSingleUrlParam(String name) { |
||||
List<String> values = urlParams.get(name); |
||||
if (values != null && values.size() == 1) { |
||||
return values.iterator().next(); |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,87 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio; |
||||
|
||||
import io.netty.buffer.ByteBufInputStream; |
||||
import io.netty.buffer.ByteBufOutputStream; |
||||
|
||||
import java.io.IOException; |
||||
import java.util.List; |
||||
|
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
import com.fr.third.socketio.protocol.AckArgs; |
||||
import com.fr.third.socketio.protocol.JsonSupport; |
||||
|
||||
class JsonSupportWrapper implements JsonSupport { |
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(JsonSupportWrapper.class); |
||||
|
||||
private final JsonSupport delegate; |
||||
|
||||
JsonSupportWrapper(JsonSupport delegate) { |
||||
this.delegate = delegate; |
||||
} |
||||
|
||||
@Override |
||||
public AckArgs readAckArgs(ByteBufInputStream src, AckCallback<?> callback) throws IOException { |
||||
try { |
||||
return delegate.readAckArgs(src, callback); |
||||
} catch (Exception e) { |
||||
src.reset(); |
||||
log.error("Can't read ack args: " + src.readLine() + " for type: " + callback.getResultClass(), e); |
||||
throw new IOException(e); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public <T> T readValue(String namespaceName, ByteBufInputStream src, Class<T> valueType) throws IOException { |
||||
try { |
||||
return delegate.readValue(namespaceName, src, valueType); |
||||
} catch (Exception e) { |
||||
src.reset(); |
||||
log.error("Can't read value: " + src.readLine() + " for type: " + valueType, e); |
||||
throw new IOException(e); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void writeValue(ByteBufOutputStream out, Object value) throws IOException { |
||||
try { |
||||
delegate.writeValue(out, value); |
||||
} catch (Exception e) { |
||||
log.error("Can't write value: " + value, e); |
||||
throw new IOException(e); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void addEventMapping(String namespaceName, String eventName, Class<?> ... eventClass) { |
||||
delegate.addEventMapping(namespaceName, eventName, eventClass); |
||||
} |
||||
|
||||
@Override |
||||
public void removeEventMapping(String namespaceName, String eventName) { |
||||
delegate.removeEventMapping(namespaceName, eventName); |
||||
} |
||||
|
||||
@Override |
||||
public List<byte[]> getArrays() { |
||||
return delegate.getArrays(); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,35 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio; |
||||
|
||||
/** |
||||
* Multi type ack callback used in case of multiple ack arguments |
||||
* |
||||
*/ |
||||
public abstract class MultiTypeAckCallback extends AckCallback<MultiTypeArgs> { |
||||
|
||||
private Class<?>[] resultClasses; |
||||
|
||||
public MultiTypeAckCallback(Class<?> ... resultClasses) { |
||||
super(MultiTypeArgs.class); |
||||
this.resultClasses = resultClasses; |
||||
} |
||||
|
||||
public Class<?>[] getResultClasses() { |
||||
return resultClasses; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,69 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio; |
||||
|
||||
import java.util.Iterator; |
||||
import java.util.List; |
||||
|
||||
public class MultiTypeArgs implements Iterable<Object> { |
||||
|
||||
private final List<Object> args; |
||||
|
||||
public MultiTypeArgs(List<Object> args) { |
||||
super(); |
||||
this.args = args; |
||||
} |
||||
|
||||
public boolean isEmpty() { |
||||
return size() == 0; |
||||
} |
||||
|
||||
public int size() { |
||||
return args.size(); |
||||
} |
||||
|
||||
public List<Object> getArgs() { |
||||
return args; |
||||
} |
||||
|
||||
public <T> T first() { |
||||
return get(0); |
||||
} |
||||
|
||||
public <T> T second() { |
||||
return get(1); |
||||
} |
||||
|
||||
/** |
||||
* "index out of bounds"-safe method for getting elements |
||||
* |
||||
* @param <T> type of argument |
||||
* @param index to get |
||||
* @return argument |
||||
*/ |
||||
public <T> T get(int index) { |
||||
if (size() <= index) { |
||||
return null; |
||||
} |
||||
return (T) args.get(index); |
||||
} |
||||
|
||||
@Override |
||||
public Iterator<Object> iterator() { |
||||
return args.iterator(); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,89 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio; |
||||
|
||||
/** |
||||
* TCP socket configuration contains configuration for main server channel |
||||
* and client channels |
||||
* |
||||
* @see java.net.SocketOptions |
||||
*/ |
||||
public class SocketConfig { |
||||
|
||||
private boolean tcpNoDelay = true; |
||||
|
||||
private int tcpSendBufferSize = -1; |
||||
|
||||
private int tcpReceiveBufferSize = -1; |
||||
|
||||
private boolean tcpKeepAlive = false; |
||||
|
||||
private int soLinger = -1; |
||||
|
||||
private boolean reuseAddress = false; |
||||
|
||||
private int acceptBackLog = 1024; |
||||
|
||||
public boolean isTcpNoDelay() { |
||||
return tcpNoDelay; |
||||
} |
||||
public void setTcpNoDelay(boolean tcpNoDelay) { |
||||
this.tcpNoDelay = tcpNoDelay; |
||||
} |
||||
|
||||
public int getTcpSendBufferSize() { |
||||
return tcpSendBufferSize; |
||||
} |
||||
public void setTcpSendBufferSize(int tcpSendBufferSize) { |
||||
this.tcpSendBufferSize = tcpSendBufferSize; |
||||
} |
||||
|
||||
public int getTcpReceiveBufferSize() { |
||||
return tcpReceiveBufferSize; |
||||
} |
||||
public void setTcpReceiveBufferSize(int tcpReceiveBufferSize) { |
||||
this.tcpReceiveBufferSize = tcpReceiveBufferSize; |
||||
} |
||||
|
||||
public boolean isTcpKeepAlive() { |
||||
return tcpKeepAlive; |
||||
} |
||||
public void setTcpKeepAlive(boolean tcpKeepAlive) { |
||||
this.tcpKeepAlive = tcpKeepAlive; |
||||
} |
||||
|
||||
public int getSoLinger() { |
||||
return soLinger; |
||||
} |
||||
public void setSoLinger(int soLinger) { |
||||
this.soLinger = soLinger; |
||||
} |
||||
|
||||
public boolean isReuseAddress() { |
||||
return reuseAddress; |
||||
} |
||||
public void setReuseAddress(boolean reuseAddress) { |
||||
this.reuseAddress = reuseAddress; |
||||
} |
||||
|
||||
public int getAcceptBackLog() { |
||||
return acceptBackLog; |
||||
} |
||||
public void setAcceptBackLog(int acceptBackLog) { |
||||
this.acceptBackLog = acceptBackLog; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,236 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio; |
||||
|
||||
import java.security.KeyStore; |
||||
|
||||
import javax.net.ssl.KeyManagerFactory; |
||||
import javax.net.ssl.SSLContext; |
||||
import javax.net.ssl.SSLEngine; |
||||
import javax.net.ssl.TrustManager; |
||||
import javax.net.ssl.TrustManagerFactory; |
||||
|
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
import com.fr.third.socketio.ack.AckManager; |
||||
import com.fr.third.socketio.handler.AuthorizeHandler; |
||||
import com.fr.third.socketio.handler.ClientHead; |
||||
import com.fr.third.socketio.handler.ClientsBox; |
||||
import com.fr.third.socketio.handler.EncoderHandler; |
||||
import com.fr.third.socketio.handler.InPacketHandler; |
||||
import com.fr.third.socketio.handler.PacketListener; |
||||
import com.fr.third.socketio.handler.WrongUrlHandler; |
||||
import com.fr.third.socketio.namespace.NamespacesHub; |
||||
import com.fr.third.socketio.protocol.JsonSupport; |
||||
import com.fr.third.socketio.protocol.PacketDecoder; |
||||
import com.fr.third.socketio.protocol.PacketEncoder; |
||||
import com.fr.third.socketio.scheduler.CancelableScheduler; |
||||
import com.fr.third.socketio.scheduler.HashedWheelTimeoutScheduler; |
||||
import com.fr.third.socketio.store.StoreFactory; |
||||
import com.fr.third.socketio.store.pubsub.DisconnectMessage; |
||||
import com.fr.third.socketio.store.pubsub.PubSubType; |
||||
import com.fr.third.socketio.transport.PollingTransport; |
||||
import com.fr.third.socketio.transport.WebSocketTransport; |
||||
|
||||
import io.netty.channel.Channel; |
||||
import io.netty.channel.ChannelHandlerContext; |
||||
import io.netty.channel.ChannelInitializer; |
||||
import io.netty.channel.ChannelPipeline; |
||||
import io.netty.handler.codec.http.HttpContentCompressor; |
||||
import io.netty.handler.codec.http.HttpMessage; |
||||
import io.netty.handler.codec.http.HttpObjectAggregator; |
||||
import io.netty.handler.codec.http.HttpRequestDecoder; |
||||
import io.netty.handler.codec.http.HttpResponseEncoder; |
||||
import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketServerCompressionHandler; |
||||
import io.netty.handler.ssl.SslHandler; |
||||
|
||||
public class SocketIOChannelInitializer extends ChannelInitializer<Channel> implements DisconnectableHub { |
||||
|
||||
public static final String SOCKETIO_ENCODER = "socketioEncoder"; |
||||
public static final String WEB_SOCKET_TRANSPORT_COMPRESSION = "webSocketTransportCompression"; |
||||
public static final String WEB_SOCKET_TRANSPORT = "webSocketTransport"; |
||||
public static final String WEB_SOCKET_AGGREGATOR = "webSocketAggregator"; |
||||
public static final String XHR_POLLING_TRANSPORT = "xhrPollingTransport"; |
||||
public static final String AUTHORIZE_HANDLER = "authorizeHandler"; |
||||
public static final String PACKET_HANDLER = "packetHandler"; |
||||
public static final String HTTP_ENCODER = "httpEncoder"; |
||||
public static final String HTTP_COMPRESSION = "httpCompression"; |
||||
public static final String HTTP_AGGREGATOR = "httpAggregator"; |
||||
public static final String HTTP_REQUEST_DECODER = "httpDecoder"; |
||||
public static final String SSL_HANDLER = "ssl"; |
||||
|
||||
public static final String RESOURCE_HANDLER = "resourceHandler"; |
||||
public static final String WRONG_URL_HANDLER = "wrongUrlBlocker"; |
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(SocketIOChannelInitializer.class); |
||||
|
||||
private AckManager ackManager; |
||||
|
||||
private ClientsBox clientsBox = new ClientsBox(); |
||||
private AuthorizeHandler authorizeHandler; |
||||
private PollingTransport xhrPollingTransport; |
||||
private WebSocketTransport webSocketTransport; |
||||
private WebSocketServerCompressionHandler webSocketTransportCompression = new WebSocketServerCompressionHandler(); |
||||
private EncoderHandler encoderHandler; |
||||
private WrongUrlHandler wrongUrlHandler; |
||||
|
||||
private CancelableScheduler scheduler = new HashedWheelTimeoutScheduler(); |
||||
|
||||
private InPacketHandler packetHandler; |
||||
private SSLContext sslContext; |
||||
private Configuration configuration; |
||||
|
||||
@Override |
||||
public void handlerAdded(ChannelHandlerContext ctx) { |
||||
scheduler.update(ctx); |
||||
} |
||||
|
||||
public void start(Configuration configuration, NamespacesHub namespacesHub) { |
||||
this.configuration = configuration; |
||||
|
||||
ackManager = new AckManager(scheduler); |
||||
|
||||
JsonSupport jsonSupport = configuration.getJsonSupport(); |
||||
PacketEncoder encoder = new PacketEncoder(configuration, jsonSupport); |
||||
PacketDecoder decoder = new PacketDecoder(jsonSupport, ackManager); |
||||
|
||||
String connectPath = configuration.getContext() + "/"; |
||||
|
||||
boolean isSsl = configuration.getKeyStore() != null; |
||||
if (isSsl) { |
||||
try { |
||||
sslContext = createSSLContext(configuration); |
||||
} catch (Exception e) { |
||||
throw new IllegalStateException(e); |
||||
} |
||||
} |
||||
|
||||
StoreFactory factory = configuration.getStoreFactory(); |
||||
authorizeHandler = new AuthorizeHandler(connectPath, scheduler, configuration, namespacesHub, factory, this, ackManager, clientsBox); |
||||
factory.init(namespacesHub, authorizeHandler, jsonSupport); |
||||
xhrPollingTransport = new PollingTransport(decoder, authorizeHandler, clientsBox); |
||||
webSocketTransport = new WebSocketTransport(isSsl, authorizeHandler, configuration, scheduler, clientsBox); |
||||
|
||||
PacketListener packetListener = new PacketListener(ackManager, namespacesHub, xhrPollingTransport, scheduler); |
||||
|
||||
|
||||
packetHandler = new InPacketHandler(packetListener, decoder, namespacesHub, configuration.getExceptionListener()); |
||||
|
||||
try { |
||||
encoderHandler = new EncoderHandler(configuration, encoder); |
||||
} catch (Exception e) { |
||||
throw new IllegalStateException(e); |
||||
} |
||||
|
||||
wrongUrlHandler = new WrongUrlHandler(); |
||||
} |
||||
|
||||
@Override |
||||
protected void initChannel(Channel ch) throws Exception { |
||||
ChannelPipeline pipeline = ch.pipeline(); |
||||
addSslHandler(pipeline); |
||||
addSocketioHandlers(pipeline); |
||||
} |
||||
|
||||
/** |
||||
* Adds the ssl handler |
||||
* |
||||
* @param pipeline - channel pipeline |
||||
*/ |
||||
protected void addSslHandler(ChannelPipeline pipeline) { |
||||
if (sslContext != null) { |
||||
SSLEngine engine = sslContext.createSSLEngine(); |
||||
engine.setUseClientMode(false); |
||||
pipeline.addLast(SSL_HANDLER, new SslHandler(engine)); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Adds the socketio channel handlers |
||||
* |
||||
* @param pipeline - channel pipeline |
||||
*/ |
||||
protected void addSocketioHandlers(ChannelPipeline pipeline) { |
||||
pipeline.addLast(HTTP_REQUEST_DECODER, new HttpRequestDecoder()); |
||||
pipeline.addLast(HTTP_AGGREGATOR, new HttpObjectAggregator(configuration.getMaxHttpContentLength()) { |
||||
@Override |
||||
protected Object newContinueResponse(HttpMessage start, int maxContentLength, |
||||
ChannelPipeline pipeline) { |
||||
return null; |
||||
} |
||||
|
||||
}); |
||||
pipeline.addLast(HTTP_ENCODER, new HttpResponseEncoder()); |
||||
|
||||
if (configuration.isHttpCompression()) { |
||||
pipeline.addLast(HTTP_COMPRESSION, new HttpContentCompressor()); |
||||
} |
||||
|
||||
pipeline.addLast(PACKET_HANDLER, packetHandler); |
||||
|
||||
pipeline.addLast(AUTHORIZE_HANDLER, authorizeHandler); |
||||
pipeline.addLast(XHR_POLLING_TRANSPORT, xhrPollingTransport); |
||||
// TODO use single instance when https://github.com/netty/netty/issues/4755 will be resolved
|
||||
if (configuration.isWebsocketCompression()) { |
||||
pipeline.addLast(WEB_SOCKET_TRANSPORT_COMPRESSION, new WebSocketServerCompressionHandler()); |
||||
} |
||||
pipeline.addLast(WEB_SOCKET_TRANSPORT, webSocketTransport); |
||||
|
||||
pipeline.addLast(SOCKETIO_ENCODER, encoderHandler); |
||||
|
||||
pipeline.addLast(WRONG_URL_HANDLER, wrongUrlHandler); |
||||
} |
||||
|
||||
private SSLContext createSSLContext(Configuration configuration) throws Exception { |
||||
TrustManager[] managers = null; |
||||
if (configuration.getTrustStore() != null) { |
||||
KeyStore ts = KeyStore.getInstance(configuration.getTrustStoreFormat()); |
||||
ts.load(configuration.getTrustStore(), configuration.getTrustStorePassword().toCharArray()); |
||||
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); |
||||
tmf.init(ts); |
||||
managers = tmf.getTrustManagers(); |
||||
} |
||||
|
||||
KeyStore ks = KeyStore.getInstance(configuration.getKeyStoreFormat()); |
||||
ks.load(configuration.getKeyStore(), configuration.getKeyStorePassword().toCharArray()); |
||||
|
||||
KeyManagerFactory kmf = KeyManagerFactory.getInstance(configuration.getKeyManagerFactoryAlgorithm()); |
||||
kmf.init(ks, configuration.getKeyStorePassword().toCharArray()); |
||||
|
||||
SSLContext serverContext = SSLContext.getInstance(configuration.getSSLProtocol()); |
||||
serverContext.init(kmf.getKeyManagers(), managers, null); |
||||
return serverContext; |
||||
} |
||||
|
||||
@Override |
||||
public void onDisconnect(ClientHead client) { |
||||
ackManager.onDisconnect(client); |
||||
authorizeHandler.onDisconnect(client); |
||||
configuration.getStoreFactory().onDisconnect(client); |
||||
|
||||
configuration.getStoreFactory().pubSubStore().publish(PubSubType.DISCONNECT, new DisconnectMessage(client.getSessionId())); |
||||
|
||||
log.debug("Client with sessionId: {} disconnected", client.getSessionId()); |
||||
} |
||||
|
||||
public void stop() { |
||||
StoreFactory factory = configuration.getStoreFactory(); |
||||
factory.shutdown(); |
||||
scheduler.shutdown(); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,112 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio; |
||||
|
||||
import java.net.SocketAddress; |
||||
import java.util.Set; |
||||
import java.util.UUID; |
||||
|
||||
import com.fr.third.socketio.protocol.Packet; |
||||
import com.fr.third.socketio.store.Store; |
||||
|
||||
|
||||
/** |
||||
* Fully thread-safe. |
||||
* |
||||
*/ |
||||
public interface SocketIOClient extends ClientOperations, Store { |
||||
|
||||
/** |
||||
* Handshake data used during client connection |
||||
* |
||||
* @return HandshakeData |
||||
*/ |
||||
HandshakeData getHandshakeData(); |
||||
|
||||
/** |
||||
* Current client transport protocol |
||||
* |
||||
* @return transport protocol |
||||
*/ |
||||
Transport getTransport(); |
||||
|
||||
/** |
||||
* Send event with ack callback |
||||
* |
||||
* @param name - event name |
||||
* @param data - event data |
||||
* @param ackCallback - ack callback |
||||
*/ |
||||
void sendEvent(String name, AckCallback<?> ackCallback, Object ... data); |
||||
|
||||
/** |
||||
* Send packet with ack callback |
||||
* |
||||
* @param packet - packet to send |
||||
* @param ackCallback - ack callback |
||||
*/ |
||||
void send(Packet packet, AckCallback<?> ackCallback); |
||||
|
||||
/** |
||||
* Client namespace |
||||
* |
||||
* @return - namespace |
||||
*/ |
||||
SocketIONamespace getNamespace(); |
||||
|
||||
/** |
||||
* Client session id, uses {@link UUID} object |
||||
* |
||||
* @return - session id |
||||
*/ |
||||
UUID getSessionId(); |
||||
|
||||
/** |
||||
* Get client remote address |
||||
* |
||||
* @return remote address |
||||
*/ |
||||
SocketAddress getRemoteAddress(); |
||||
|
||||
/** |
||||
* Check is underlying channel open |
||||
* |
||||
* @return <code>true</code> if channel open, otherwise <code>false</code> |
||||
*/ |
||||
boolean isChannelOpen(); |
||||
|
||||
/** |
||||
* Join client to room |
||||
* |
||||
* @param room - name of room |
||||
*/ |
||||
void joinRoom(String room); |
||||
|
||||
/** |
||||
* Join client to room |
||||
* |
||||
* @param room - name of room |
||||
*/ |
||||
void leaveRoom(String room); |
||||
|
||||
/** |
||||
* Get all rooms a client is joined in. |
||||
* |
||||
* @return name of rooms |
||||
*/ |
||||
Set<String> getAllRooms(); |
||||
|
||||
} |
@ -0,0 +1,50 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio; |
||||
|
||||
import java.util.Collection; |
||||
import java.util.UUID; |
||||
|
||||
import com.fr.third.socketio.listener.ClientListeners; |
||||
|
||||
/** |
||||
* Fully thread-safe. |
||||
* |
||||
*/ |
||||
public interface SocketIONamespace extends ClientListeners { |
||||
|
||||
String getName(); |
||||
|
||||
BroadcastOperations getBroadcastOperations(); |
||||
|
||||
BroadcastOperations getRoomOperations(String room); |
||||
|
||||
/** |
||||
* Get all clients connected to namespace |
||||
* |
||||
* @return collection of clients |
||||
*/ |
||||
Collection<SocketIOClient> getAllClients(); |
||||
|
||||
/** |
||||
* Get client by uuid connected to namespace |
||||
* |
||||
* @param uuid - id of client |
||||
* @return client |
||||
*/ |
||||
SocketIOClient getClient(UUID uuid); |
||||
|
||||
} |
@ -0,0 +1,269 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio; |
||||
|
||||
import com.fr.third.socketio.listener.*; |
||||
import com.fr.third.socketio.listener.ClientListeners; |
||||
import com.fr.third.socketio.listener.ConnectListener; |
||||
import com.fr.third.socketio.listener.DataListener; |
||||
import com.fr.third.socketio.listener.DisconnectListener; |
||||
import com.fr.third.socketio.listener.MultiTypeEventListener; |
||||
import com.fr.third.socketio.listener.PingListener; |
||||
import io.netty.bootstrap.ServerBootstrap; |
||||
import io.netty.channel.ChannelOption; |
||||
import io.netty.channel.EventLoopGroup; |
||||
import io.netty.channel.FixedRecvByteBufAllocator; |
||||
import io.netty.channel.ServerChannel; |
||||
import io.netty.channel.epoll.EpollEventLoopGroup; |
||||
import io.netty.channel.epoll.EpollServerSocketChannel; |
||||
import io.netty.channel.nio.NioEventLoopGroup; |
||||
import io.netty.channel.socket.nio.NioServerSocketChannel; |
||||
import io.netty.util.concurrent.Future; |
||||
import io.netty.util.concurrent.FutureListener; |
||||
|
||||
import java.net.InetSocketAddress; |
||||
import java.util.Collection; |
||||
import java.util.UUID; |
||||
|
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
import com.fr.third.socketio.namespace.Namespace; |
||||
import com.fr.third.socketio.namespace.NamespacesHub; |
||||
|
||||
/** |
||||
* Fully thread-safe. |
||||
* |
||||
*/ |
||||
public class SocketIOServer implements ClientListeners { |
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(SocketIOServer.class); |
||||
|
||||
private final Configuration configCopy; |
||||
private final Configuration configuration; |
||||
|
||||
private final NamespacesHub namespacesHub; |
||||
private final SocketIONamespace mainNamespace; |
||||
|
||||
private SocketIOChannelInitializer pipelineFactory = new SocketIOChannelInitializer(); |
||||
|
||||
private EventLoopGroup bossGroup; |
||||
private EventLoopGroup workerGroup; |
||||
|
||||
public SocketIOServer(Configuration configuration) { |
||||
this.configuration = configuration; |
||||
this.configCopy = new Configuration(configuration); |
||||
namespacesHub = new NamespacesHub(configCopy); |
||||
mainNamespace = addNamespace(Namespace.DEFAULT_NAME); |
||||
} |
||||
|
||||
public void setPipelineFactory(SocketIOChannelInitializer pipelineFactory) { |
||||
this.pipelineFactory = pipelineFactory; |
||||
} |
||||
|
||||
/** |
||||
* Get all clients connected to default namespace |
||||
* |
||||
* @return clients collection |
||||
*/ |
||||
public Collection<SocketIOClient> getAllClients() { |
||||
return namespacesHub.get(Namespace.DEFAULT_NAME).getAllClients(); |
||||
} |
||||
|
||||
/** |
||||
* Get client by uuid from default namespace |
||||
* |
||||
* @param uuid - id of client |
||||
* @return client |
||||
*/ |
||||
public SocketIOClient getClient(UUID uuid) { |
||||
return namespacesHub.get(Namespace.DEFAULT_NAME).getClient(uuid); |
||||
} |
||||
|
||||
/** |
||||
* Get all namespaces |
||||
* |
||||
* @return namespaces collection |
||||
*/ |
||||
public Collection<SocketIONamespace> getAllNamespaces() { |
||||
return namespacesHub.getAllNamespaces(); |
||||
} |
||||
|
||||
public BroadcastOperations getBroadcastOperations() { |
||||
return new BroadcastOperations(getAllClients(), configCopy.getStoreFactory()); |
||||
} |
||||
|
||||
/** |
||||
* Get broadcast operations for clients within |
||||
* room by <code>room</code> name |
||||
* |
||||
* @param room - name of room |
||||
* @return broadcast operations |
||||
*/ |
||||
public BroadcastOperations getRoomOperations(String room) { |
||||
Iterable<SocketIOClient> clients = namespacesHub.getRoomClients(room); |
||||
return new BroadcastOperations(clients, configCopy.getStoreFactory()); |
||||
} |
||||
|
||||
/** |
||||
* Start server |
||||
*/ |
||||
public void start() { |
||||
startAsync().syncUninterruptibly(); |
||||
} |
||||
|
||||
/** |
||||
* Start server asynchronously |
||||
* |
||||
* @return void |
||||
*/ |
||||
public Future<Void> startAsync() { |
||||
log.info("Session store / pubsub factory used: {}", configCopy.getStoreFactory()); |
||||
initGroups(); |
||||
|
||||
pipelineFactory.start(configCopy, namespacesHub); |
||||
|
||||
Class<? extends ServerChannel> channelClass = NioServerSocketChannel.class; |
||||
if (configCopy.isUseLinuxNativeEpoll()) { |
||||
channelClass = EpollServerSocketChannel.class; |
||||
} |
||||
|
||||
ServerBootstrap b = new ServerBootstrap(); |
||||
b.group(bossGroup, workerGroup) |
||||
.channel(channelClass) |
||||
.childHandler(pipelineFactory); |
||||
applyConnectionOptions(b); |
||||
|
||||
InetSocketAddress addr = new InetSocketAddress(configCopy.getPort()); |
||||
if (configCopy.getHostname() != null) { |
||||
addr = new InetSocketAddress(configCopy.getHostname(), configCopy.getPort()); |
||||
} |
||||
|
||||
return b.bind(addr).addListener(new FutureListener<Void>() { |
||||
@Override |
||||
public void operationComplete(Future<Void> future) throws Exception { |
||||
if (future.isSuccess()) { |
||||
log.info("SocketIO server started at port: {}", configCopy.getPort()); |
||||
} else { |
||||
log.error("SocketIO server start failed at port: {}!", configCopy.getPort()); |
||||
} |
||||
} |
||||
}); |
||||
} |
||||
|
||||
protected void applyConnectionOptions(ServerBootstrap bootstrap) { |
||||
SocketConfig config = configCopy.getSocketConfig(); |
||||
bootstrap.childOption(ChannelOption.TCP_NODELAY, config.isTcpNoDelay()); |
||||
if (config.getTcpSendBufferSize() != -1) { |
||||
bootstrap.childOption(ChannelOption.SO_SNDBUF, config.getTcpSendBufferSize()); |
||||
} |
||||
if (config.getTcpReceiveBufferSize() != -1) { |
||||
bootstrap.childOption(ChannelOption.SO_RCVBUF, config.getTcpReceiveBufferSize()); |
||||
bootstrap.childOption(ChannelOption.RCVBUF_ALLOCATOR, new FixedRecvByteBufAllocator(config.getTcpReceiveBufferSize())); |
||||
} |
||||
bootstrap.childOption(ChannelOption.SO_KEEPALIVE, config.isTcpKeepAlive()); |
||||
bootstrap.childOption(ChannelOption.SO_LINGER, config.getSoLinger()); |
||||
|
||||
bootstrap.option(ChannelOption.SO_REUSEADDR, config.isReuseAddress()); |
||||
bootstrap.option(ChannelOption.SO_BACKLOG, config.getAcceptBackLog()); |
||||
} |
||||
|
||||
protected void initGroups() { |
||||
if (configCopy.isUseLinuxNativeEpoll()) { |
||||
bossGroup = new EpollEventLoopGroup(configCopy.getBossThreads()); |
||||
workerGroup = new EpollEventLoopGroup(configCopy.getWorkerThreads()); |
||||
} else { |
||||
bossGroup = new NioEventLoopGroup(configCopy.getBossThreads()); |
||||
workerGroup = new NioEventLoopGroup(configCopy.getWorkerThreads()); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Stop server |
||||
*/ |
||||
public void stop() { |
||||
bossGroup.shutdownGracefully().syncUninterruptibly(); |
||||
workerGroup.shutdownGracefully().syncUninterruptibly(); |
||||
|
||||
pipelineFactory.stop(); |
||||
log.info("SocketIO server stopped"); |
||||
} |
||||
|
||||
public SocketIONamespace addNamespace(String name) { |
||||
return namespacesHub.create(name); |
||||
} |
||||
|
||||
public SocketIONamespace getNamespace(String name) { |
||||
return namespacesHub.get(name); |
||||
} |
||||
|
||||
public void removeNamespace(String name) { |
||||
namespacesHub.remove(name); |
||||
} |
||||
|
||||
/** |
||||
* Allows to get configuration provided |
||||
* during server creation. Further changes on |
||||
* this object not affect server. |
||||
* |
||||
* @return Configuration object |
||||
*/ |
||||
public Configuration getConfiguration() { |
||||
return configuration; |
||||
} |
||||
|
||||
@Override |
||||
public void addMultiTypeEventListener(String eventName, MultiTypeEventListener listener, Class<?>... eventClass) { |
||||
mainNamespace.addMultiTypeEventListener(eventName, listener, eventClass); |
||||
} |
||||
|
||||
@Override |
||||
public <T> void addEventListener(String eventName, Class<T> eventClass, DataListener<T> listener) { |
||||
mainNamespace.addEventListener(eventName, eventClass, listener); |
||||
} |
||||
|
||||
@Override |
||||
public void removeAllListeners(String eventName) { |
||||
mainNamespace.removeAllListeners(eventName); |
||||
} |
||||
|
||||
@Override |
||||
public void addDisconnectListener(DisconnectListener listener) { |
||||
mainNamespace.addDisconnectListener(listener); |
||||
} |
||||
|
||||
@Override |
||||
public void addConnectListener(ConnectListener listener) { |
||||
mainNamespace.addConnectListener(listener); |
||||
} |
||||
|
||||
@Override |
||||
public void addPingListener(PingListener listener) { |
||||
mainNamespace.addPingListener(listener); |
||||
} |
||||
|
||||
@Override |
||||
public void addListeners(Object listeners) { |
||||
mainNamespace.addListeners(listeners); |
||||
} |
||||
|
||||
@Override |
||||
public void addListeners(Object listeners, Class<?> listenersClass) { |
||||
mainNamespace.addListeners(listeners, listenersClass); |
||||
} |
||||
|
||||
|
||||
} |
@ -0,0 +1,45 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio; |
||||
|
||||
import com.fr.third.socketio.transport.WebSocketTransport; |
||||
import com.fr.third.socketio.transport.PollingTransport; |
||||
|
||||
public enum Transport { |
||||
|
||||
WEBSOCKET(WebSocketTransport.NAME), |
||||
POLLING(PollingTransport.NAME); |
||||
|
||||
private final String value; |
||||
|
||||
Transport(String value) { |
||||
this.value = value; |
||||
} |
||||
|
||||
public String getValue() { |
||||
return value; |
||||
} |
||||
|
||||
public static Transport byName(String value) { |
||||
for (Transport t : Transport.values()) { |
||||
if (t.getValue().equals(value)) { |
||||
return t; |
||||
} |
||||
} |
||||
throw new IllegalArgumentException("Can't find " + value + " transport"); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,39 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio; |
||||
|
||||
/** |
||||
* Base ack callback with {@link Void} class as type. |
||||
* |
||||
*/ |
||||
public abstract class VoidAckCallback extends AckCallback<Void> { |
||||
|
||||
public VoidAckCallback() { |
||||
super(Void.class); |
||||
} |
||||
|
||||
public VoidAckCallback(int timeout) { |
||||
super(Void.class, timeout); |
||||
} |
||||
|
||||
@Override |
||||
public final void onSuccess(Void result) { |
||||
onSuccess(); |
||||
} |
||||
|
||||
protected abstract void onSuccess(); |
||||
|
||||
} |
@ -0,0 +1,186 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.ack; |
||||
|
||||
import com.fr.third.socketio.AckCallback; |
||||
import com.fr.third.socketio.Disconnectable; |
||||
import com.fr.third.socketio.MultiTypeAckCallback; |
||||
import com.fr.third.socketio.MultiTypeArgs; |
||||
import com.fr.third.socketio.SocketIOClient; |
||||
import com.fr.third.socketio.handler.ClientHead; |
||||
import com.fr.third.socketio.protocol.Packet; |
||||
import com.fr.third.socketio.scheduler.CancelableScheduler; |
||||
import com.fr.third.socketio.scheduler.SchedulerKey; |
||||
import io.netty.util.internal.PlatformDependent; |
||||
|
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.Set; |
||||
import java.util.UUID; |
||||
import java.util.concurrent.TimeUnit; |
||||
import java.util.concurrent.atomic.AtomicLong; |
||||
|
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
public class AckManager implements Disconnectable { |
||||
|
||||
class AckEntry { |
||||
|
||||
final Map<Long, AckCallback<?>> ackCallbacks = PlatformDependent.newConcurrentHashMap(); |
||||
final AtomicLong ackIndex = new AtomicLong(-1); |
||||
|
||||
public long addAckCallback(AckCallback<?> callback) { |
||||
long index = ackIndex.incrementAndGet(); |
||||
ackCallbacks.put(index, callback); |
||||
return index; |
||||
} |
||||
|
||||
public Set<Long> getAckIndexes() { |
||||
return ackCallbacks.keySet(); |
||||
} |
||||
|
||||
public AckCallback<?> getAckCallback(long index) { |
||||
return ackCallbacks.get(index); |
||||
} |
||||
|
||||
public AckCallback<?> removeCallback(long index) { |
||||
return ackCallbacks.remove(index); |
||||
} |
||||
|
||||
public void initAckIndex(long index) { |
||||
ackIndex.compareAndSet(-1, index); |
||||
} |
||||
|
||||
} |
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(AckManager.class); |
||||
|
||||
private final Map<UUID, AckEntry> ackEntries = PlatformDependent.newConcurrentHashMap(); |
||||
|
||||
private final CancelableScheduler scheduler; |
||||
|
||||
public AckManager(CancelableScheduler scheduler) { |
||||
super(); |
||||
this.scheduler = scheduler; |
||||
} |
||||
|
||||
public void initAckIndex(UUID sessionId, long index) { |
||||
AckEntry ackEntry = getAckEntry(sessionId); |
||||
ackEntry.initAckIndex(index); |
||||
} |
||||
|
||||
private AckEntry getAckEntry(UUID sessionId) { |
||||
AckEntry ackEntry = ackEntries.get(sessionId); |
||||
if (ackEntry == null) { |
||||
ackEntry = new AckEntry(); |
||||
AckEntry oldAckEntry = ackEntries.put(sessionId, ackEntry); |
||||
if (oldAckEntry != null) { |
||||
ackEntry = oldAckEntry; |
||||
} |
||||
} |
||||
return ackEntry; |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
public void onAck(SocketIOClient client, Packet packet) { |
||||
AckSchedulerKey key = new AckSchedulerKey(SchedulerKey.Type.ACK_TIMEOUT, client.getSessionId(), packet.getAckId()); |
||||
scheduler.cancel(key); |
||||
|
||||
AckCallback callback = removeCallback(client.getSessionId(), packet.getAckId()); |
||||
if (callback == null) { |
||||
return; |
||||
} |
||||
if (callback instanceof MultiTypeAckCallback) { |
||||
callback.onSuccess(new MultiTypeArgs(packet.<List<Object>>getData())); |
||||
} else { |
||||
Object param = null; |
||||
List<Object> args = packet.getData(); |
||||
if (!args.isEmpty()) { |
||||
param = args.get(0); |
||||
} |
||||
if (args.size() > 1) { |
||||
log.error("Wrong ack args amount. Should be only one argument, but current amount is: {}. Ack id: {}, sessionId: {}", |
||||
args.size(), packet.getAckId(), client.getSessionId()); |
||||
} |
||||
callback.onSuccess(param); |
||||
} |
||||
} |
||||
|
||||
private AckCallback<?> removeCallback(UUID sessionId, long index) { |
||||
AckEntry ackEntry = ackEntries.get(sessionId); |
||||
// may be null if client disconnected
|
||||
// before timeout occurs
|
||||
if (ackEntry != null) { |
||||
return ackEntry.removeCallback(index); |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
public AckCallback<?> getCallback(UUID sessionId, long index) { |
||||
AckEntry ackEntry = getAckEntry(sessionId); |
||||
return ackEntry.getAckCallback(index); |
||||
} |
||||
|
||||
public long registerAck(UUID sessionId, AckCallback<?> callback) { |
||||
AckEntry ackEntry = getAckEntry(sessionId); |
||||
ackEntry.initAckIndex(0); |
||||
long index = ackEntry.addAckCallback(callback); |
||||
|
||||
if (log.isDebugEnabled()) { |
||||
log.debug("AckCallback registered with id: {} for client: {}", index, sessionId); |
||||
} |
||||
|
||||
scheduleTimeout(index, sessionId, callback); |
||||
|
||||
return index; |
||||
} |
||||
|
||||
private void scheduleTimeout(final long index, final UUID sessionId, AckCallback<?> callback) { |
||||
if (callback.getTimeout() == -1) { |
||||
return; |
||||
} |
||||
SchedulerKey key = new AckSchedulerKey(SchedulerKey.Type.ACK_TIMEOUT, sessionId, index); |
||||
scheduler.scheduleCallback(key, new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
AckCallback<?> cb = removeCallback(sessionId, index); |
||||
if (cb != null) { |
||||
cb.onTimeout(); |
||||
} |
||||
} |
||||
}, callback.getTimeout(), TimeUnit.SECONDS); |
||||
} |
||||
|
||||
@Override |
||||
public void onDisconnect(ClientHead client) { |
||||
AckEntry e = ackEntries.remove(client.getSessionId()); |
||||
if (e == null) { |
||||
return; |
||||
} |
||||
|
||||
Set<Long> indexes = e.getAckIndexes(); |
||||
for (Long index : indexes) { |
||||
AckCallback<?> callback = e.getAckCallback(index); |
||||
if (callback != null) { |
||||
callback.onTimeout(); |
||||
} |
||||
SchedulerKey key = new AckSchedulerKey(SchedulerKey.Type.ACK_TIMEOUT, client.getSessionId(), index); |
||||
scheduler.cancel(key); |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,57 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.ack; |
||||
|
||||
import java.util.UUID; |
||||
|
||||
import com.fr.third.socketio.scheduler.SchedulerKey; |
||||
|
||||
public class AckSchedulerKey extends SchedulerKey { |
||||
|
||||
private final long index; |
||||
|
||||
public AckSchedulerKey(Type type, UUID sessionId, long index) { |
||||
super(type, sessionId); |
||||
this.index = index; |
||||
} |
||||
|
||||
public long getIndex() { |
||||
return index; |
||||
} |
||||
|
||||
@Override |
||||
public int hashCode() { |
||||
final int prime = 31; |
||||
int result = super.hashCode(); |
||||
result = prime * result + (int) (index ^ (index >>> 32)); |
||||
return result; |
||||
} |
||||
|
||||
@Override |
||||
public boolean equals(Object obj) { |
||||
if (this == obj) |
||||
return true; |
||||
if (!super.equals(obj)) |
||||
return false; |
||||
if (getClass() != obj.getClass()) |
||||
return false; |
||||
AckSchedulerKey other = (AckSchedulerKey) obj; |
||||
if (index != other.index) |
||||
return false; |
||||
return true; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,31 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.annotation; |
||||
|
||||
import java.lang.annotation.Annotation; |
||||
import java.lang.reflect.Method; |
||||
|
||||
import com.fr.third.socketio.namespace.Namespace; |
||||
|
||||
public interface AnnotationScanner { |
||||
|
||||
Class<? extends Annotation> getScanAnnotation(); |
||||
|
||||
void addListener(Namespace namespace, Object object, Method method, Annotation annotation); |
||||
|
||||
void validate(Method method, Class<?> clazz); |
||||
|
||||
} |
@ -0,0 +1,36 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.annotation; |
||||
|
||||
import java.lang.annotation.ElementType; |
||||
import java.lang.annotation.Retention; |
||||
import java.lang.annotation.RetentionPolicy; |
||||
import java.lang.annotation.Target; |
||||
|
||||
|
||||
/** |
||||
* Annotation that defines <b>Connect</b> handler. |
||||
* |
||||
* Arguments in method: |
||||
* |
||||
* - SocketIOClient (required) |
||||
* |
||||
*/ |
||||
@Target(ElementType.METHOD) |
||||
@Retention(RetentionPolicy.RUNTIME) |
||||
public @interface OnConnect { |
||||
|
||||
} |
@ -0,0 +1,66 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.annotation; |
||||
|
||||
import java.lang.annotation.Annotation; |
||||
import java.lang.reflect.InvocationTargetException; |
||||
import java.lang.reflect.Method; |
||||
|
||||
import com.fr.third.socketio.SocketIOClient; |
||||
import com.fr.third.socketio.handler.SocketIOException; |
||||
import com.fr.third.socketio.listener.ConnectListener; |
||||
import com.fr.third.socketio.namespace.Namespace; |
||||
|
||||
public class OnConnectScanner implements AnnotationScanner { |
||||
|
||||
@Override |
||||
public Class<? extends Annotation> getScanAnnotation() { |
||||
return OnConnect.class; |
||||
} |
||||
|
||||
@Override |
||||
public void addListener(Namespace namespace, final Object object, final Method method, Annotation annotation) { |
||||
namespace.addConnectListener(new ConnectListener() { |
||||
@Override |
||||
public void onConnect(SocketIOClient client) { |
||||
try { |
||||
method.invoke(object, client); |
||||
} catch (InvocationTargetException e) { |
||||
throw new SocketIOException(e.getCause()); |
||||
} catch (Exception e) { |
||||
throw new SocketIOException(e); |
||||
} |
||||
} |
||||
}); |
||||
} |
||||
|
||||
@Override |
||||
public void validate(Method method, Class<?> clazz) { |
||||
if (method.getParameterTypes().length != 1) { |
||||
throw new IllegalArgumentException("Wrong OnConnect listener signature: " + clazz + "." + method.getName()); |
||||
} |
||||
boolean valid = false; |
||||
for (Class<?> eventType : method.getParameterTypes()) { |
||||
if (eventType.equals(SocketIOClient.class)) { |
||||
valid = true; |
||||
} |
||||
} |
||||
if (!valid) { |
||||
throw new IllegalArgumentException("Wrong OnConnect listener signature: " + clazz + "." + method.getName()); |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,35 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.annotation; |
||||
|
||||
import java.lang.annotation.ElementType; |
||||
import java.lang.annotation.Retention; |
||||
import java.lang.annotation.RetentionPolicy; |
||||
import java.lang.annotation.Target; |
||||
|
||||
/** |
||||
* Annotation that defines <b>Disconnect</b> handler. |
||||
* |
||||
* Arguments in method: |
||||
* |
||||
* - SocketIOClient (required) |
||||
* |
||||
*/ |
||||
@Target(ElementType.METHOD) |
||||
@Retention(RetentionPolicy.RUNTIME) |
||||
public @interface OnDisconnect { |
||||
|
||||
} |
@ -0,0 +1,66 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.annotation; |
||||
|
||||
import java.lang.annotation.Annotation; |
||||
import java.lang.reflect.InvocationTargetException; |
||||
import java.lang.reflect.Method; |
||||
|
||||
import com.fr.third.socketio.SocketIOClient; |
||||
import com.fr.third.socketio.handler.SocketIOException; |
||||
import com.fr.third.socketio.listener.DisconnectListener; |
||||
import com.fr.third.socketio.namespace.Namespace; |
||||
|
||||
public class OnDisconnectScanner implements AnnotationScanner { |
||||
|
||||
@Override |
||||
public Class<? extends Annotation> getScanAnnotation() { |
||||
return OnDisconnect.class; |
||||
} |
||||
|
||||
@Override |
||||
public void addListener(Namespace namespace, final Object object, final Method method, Annotation annotation) { |
||||
namespace.addDisconnectListener(new DisconnectListener() { |
||||
@Override |
||||
public void onDisconnect(SocketIOClient client) { |
||||
try { |
||||
method.invoke(object, client); |
||||
} catch (InvocationTargetException e) { |
||||
throw new SocketIOException(e.getCause()); |
||||
} catch (Exception e) { |
||||
throw new SocketIOException(e); |
||||
} |
||||
} |
||||
}); |
||||
} |
||||
|
||||
@Override |
||||
public void validate(Method method, Class<?> clazz) { |
||||
if (method.getParameterTypes().length != 1) { |
||||
throw new IllegalArgumentException("Wrong OnDisconnect listener signature: " + clazz + "." + method.getName()); |
||||
} |
||||
boolean valid = false; |
||||
for (Class<?> eventType : method.getParameterTypes()) { |
||||
if (eventType.equals(SocketIOClient.class)) { |
||||
valid = true; |
||||
} |
||||
} |
||||
if (!valid) { |
||||
throw new IllegalArgumentException("Wrong OnDisconnect listener signature: " + clazz + "." + method.getName()); |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,45 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.annotation; |
||||
|
||||
import java.lang.annotation.ElementType; |
||||
import java.lang.annotation.Retention; |
||||
import java.lang.annotation.RetentionPolicy; |
||||
import java.lang.annotation.Target; |
||||
|
||||
/** |
||||
* Annotation that defines <b>Event</b> handler. |
||||
* The value is required, and represents event name. |
||||
* |
||||
* Arguments in method: |
||||
* |
||||
* - SocketIOClient (optional) |
||||
* - AckRequest (optional) |
||||
* - Event data (optional) |
||||
* |
||||
*/ |
||||
@Target(ElementType.METHOD) |
||||
@Retention(RetentionPolicy.RUNTIME) |
||||
public @interface OnEvent { |
||||
|
||||
/** |
||||
* Event name |
||||
* |
||||
* @return value |
||||
*/ |
||||
String value(); |
||||
|
||||
} |
@ -0,0 +1,154 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.annotation; |
||||
|
||||
import java.lang.annotation.Annotation; |
||||
import java.lang.reflect.InvocationTargetException; |
||||
import java.lang.reflect.Method; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import com.fr.third.socketio.AckRequest; |
||||
import com.fr.third.socketio.MultiTypeArgs; |
||||
import com.fr.third.socketio.SocketIOClient; |
||||
import com.fr.third.socketio.handler.SocketIOException; |
||||
import com.fr.third.socketio.listener.DataListener; |
||||
import com.fr.third.socketio.listener.MultiTypeEventListener; |
||||
import com.fr.third.socketio.namespace.Namespace; |
||||
|
||||
public class OnEventScanner implements AnnotationScanner { |
||||
|
||||
@Override |
||||
public Class<? extends Annotation> getScanAnnotation() { |
||||
return OnEvent.class; |
||||
} |
||||
|
||||
@Override |
||||
@SuppressWarnings("unchecked") |
||||
public void addListener(Namespace namespace, final Object object, final Method method, Annotation annot) { |
||||
OnEvent annotation = (OnEvent) annot; |
||||
if (annotation.value() == null || annotation.value().trim().length() == 0) { |
||||
throw new IllegalArgumentException("OnEvent \"value\" parameter is required"); |
||||
} |
||||
final int socketIOClientIndex = paramIndex(method, SocketIOClient.class); |
||||
final int ackRequestIndex = paramIndex(method, AckRequest.class); |
||||
final List<Integer> dataIndexes = dataIndexes(method); |
||||
|
||||
if (dataIndexes.size() > 1) { |
||||
List<Class<?>> classes = new ArrayList<Class<?>>(); |
||||
for (int index : dataIndexes) { |
||||
Class<?> param = method.getParameterTypes()[index]; |
||||
classes.add(param); |
||||
} |
||||
|
||||
namespace.addMultiTypeEventListener(annotation.value(), new MultiTypeEventListener() { |
||||
@Override |
||||
public void onData(SocketIOClient client, MultiTypeArgs data, AckRequest ackSender) { |
||||
try { |
||||
Object[] args = new Object[method.getParameterTypes().length]; |
||||
if (socketIOClientIndex != -1) { |
||||
args[socketIOClientIndex] = client; |
||||
} |
||||
if (ackRequestIndex != -1) { |
||||
args[ackRequestIndex] = ackSender; |
||||
} |
||||
int i = 0; |
||||
for (int index : dataIndexes) { |
||||
args[index] = data.get(i); |
||||
i++; |
||||
} |
||||
method.invoke(object, args); |
||||
} catch (InvocationTargetException e) { |
||||
throw new SocketIOException(e.getCause()); |
||||
} catch (Exception e) { |
||||
throw new SocketIOException(e); |
||||
} |
||||
} |
||||
}, classes.toArray(new Class[classes.size()])); |
||||
} else { |
||||
Class objectType = Void.class; |
||||
if (!dataIndexes.isEmpty()) { |
||||
objectType = method.getParameterTypes()[dataIndexes.iterator().next()]; |
||||
} |
||||
|
||||
namespace.addEventListener(annotation.value(), objectType, new DataListener<Object>() { |
||||
@Override |
||||
public void onData(SocketIOClient client, Object data, AckRequest ackSender) { |
||||
try { |
||||
Object[] args = new Object[method.getParameterTypes().length]; |
||||
if (socketIOClientIndex != -1) { |
||||
args[socketIOClientIndex] = client; |
||||
} |
||||
if (ackRequestIndex != -1) { |
||||
args[ackRequestIndex] = ackSender; |
||||
} |
||||
if (!dataIndexes.isEmpty()) { |
||||
int dataIndex = dataIndexes.iterator().next(); |
||||
args[dataIndex] = data; |
||||
} |
||||
method.invoke(object, args); |
||||
} catch (InvocationTargetException e) { |
||||
throw new SocketIOException(e.getCause()); |
||||
} catch (Exception e) { |
||||
throw new SocketIOException(e); |
||||
} |
||||
} |
||||
}); |
||||
} |
||||
} |
||||
|
||||
private List<Integer> dataIndexes(Method method) { |
||||
List<Integer> result = new ArrayList<Integer>(); |
||||
int index = 0; |
||||
for (Class<?> type : method.getParameterTypes()) { |
||||
if (!type.equals(AckRequest.class) && !type.equals(SocketIOClient.class)) { |
||||
result.add(index); |
||||
} |
||||
index++; |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
private int paramIndex(Method method, Class<?> clazz) { |
||||
int index = 0; |
||||
for (Class<?> type : method.getParameterTypes()) { |
||||
if (type.equals(clazz)) { |
||||
return index; |
||||
} |
||||
index++; |
||||
} |
||||
return -1; |
||||
} |
||||
|
||||
@Override |
||||
public void validate(Method method, Class<?> clazz) { |
||||
int paramsCount = method.getParameterTypes().length; |
||||
final int socketIOClientIndex = paramIndex(method, SocketIOClient.class); |
||||
final int ackRequestIndex = paramIndex(method, AckRequest.class); |
||||
List<Integer> dataIndexes = dataIndexes(method); |
||||
paramsCount -= dataIndexes.size(); |
||||
if (socketIOClientIndex != -1) { |
||||
paramsCount--; |
||||
} |
||||
if (ackRequestIndex != -1) { |
||||
paramsCount--; |
||||
} |
||||
if (paramsCount != 0) { |
||||
throw new IllegalArgumentException("Wrong OnEvent listener signature: " + clazz + "." + method.getName()); |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,105 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.annotation; |
||||
|
||||
import java.lang.annotation.Annotation; |
||||
import java.lang.reflect.Method; |
||||
import java.lang.reflect.Modifier; |
||||
import java.util.Arrays; |
||||
import java.util.List; |
||||
|
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
import com.fr.third.socketio.namespace.Namespace; |
||||
|
||||
public class ScannerEngine { |
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ScannerEngine.class); |
||||
|
||||
private static final List<? extends AnnotationScanner> annotations = |
||||
Arrays.asList(new OnConnectScanner(), new OnDisconnectScanner(), new OnEventScanner()); |
||||
|
||||
private Method findSimilarMethod(Class<?> objectClazz, Method method) { |
||||
Method[] methods = objectClazz.getDeclaredMethods(); |
||||
for (Method m : methods) { |
||||
if (isEquals(m, method)) { |
||||
return m; |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
public void scan(Namespace namespace, Object object, Class<?> clazz) |
||||
throws IllegalArgumentException { |
||||
Method[] methods = clazz.getDeclaredMethods(); |
||||
|
||||
if (!clazz.isAssignableFrom(object.getClass())) { |
||||
for (Method method : methods) { |
||||
for (AnnotationScanner annotationScanner : annotations) { |
||||
Annotation ann = method.getAnnotation(annotationScanner.getScanAnnotation()); |
||||
if (ann != null) { |
||||
annotationScanner.validate(method, clazz); |
||||
|
||||
Method m = findSimilarMethod(object.getClass(), method); |
||||
if (m != null) { |
||||
annotationScanner.addListener(namespace, object, m, ann); |
||||
} else { |
||||
log.warn("Method similar to " + method.getName() + " can't be found in " + object.getClass()); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} else { |
||||
for (Method method : methods) { |
||||
for (AnnotationScanner annotationScanner : annotations) { |
||||
Annotation ann = method.getAnnotation(annotationScanner.getScanAnnotation()); |
||||
if (ann != null) { |
||||
annotationScanner.validate(method, clazz); |
||||
makeAccessible(method); |
||||
annotationScanner.addListener(namespace, object, method, ann); |
||||
} |
||||
} |
||||
} |
||||
|
||||
if (clazz.getSuperclass() != null) { |
||||
scan(namespace, object, clazz.getSuperclass()); |
||||
} else if (clazz.isInterface()) { |
||||
for (Class<?> superIfc : clazz.getInterfaces()) { |
||||
scan(namespace, object, superIfc); |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
private boolean isEquals(Method method1, Method method2) { |
||||
if (!method1.getName().equals(method2.getName()) |
||||
|| !method1.getReturnType().equals(method2.getReturnType())) { |
||||
return false; |
||||
} |
||||
|
||||
return Arrays.equals(method1.getParameterTypes(), method2.getParameterTypes()); |
||||
} |
||||
|
||||
private void makeAccessible(Method method) { |
||||
if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers())) |
||||
&& !method.isAccessible()) { |
||||
method.setAccessible(true); |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,88 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.annotation; |
||||
|
||||
import java.lang.annotation.Annotation; |
||||
import java.lang.reflect.Method; |
||||
import java.util.Arrays; |
||||
import java.util.List; |
||||
import java.util.concurrent.atomic.AtomicBoolean; |
||||
|
||||
import com.fr.third.socketio.SocketIOServer; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
import com.fr.third.springframework.beans.BeansException; |
||||
import com.fr.third.springframework.beans.factory.config.BeanPostProcessor; |
||||
import com.fr.third.springframework.util.ReflectionUtils; |
||||
import com.fr.third.springframework.util.ReflectionUtils.MethodCallback; |
||||
import com.fr.third.springframework.util.ReflectionUtils.MethodFilter; |
||||
|
||||
public class SpringAnnotationScanner implements BeanPostProcessor { |
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(SpringAnnotationScanner.class); |
||||
|
||||
private final List<Class<? extends Annotation>> annotations = |
||||
Arrays.asList(OnConnect.class, OnDisconnect.class, OnEvent.class); |
||||
|
||||
private final SocketIOServer socketIOServer; |
||||
|
||||
private Class originalBeanClass; |
||||
|
||||
public SpringAnnotationScanner(SocketIOServer socketIOServer) { |
||||
super(); |
||||
this.socketIOServer = socketIOServer; |
||||
} |
||||
|
||||
@Override |
||||
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { |
||||
if (originalBeanClass != null) { |
||||
socketIOServer.addListeners(bean, originalBeanClass); |
||||
log.info("{} bean listeners added", beanName); |
||||
originalBeanClass = null; |
||||
} |
||||
return bean; |
||||
} |
||||
|
||||
@Override |
||||
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { |
||||
final AtomicBoolean add = new AtomicBoolean(); |
||||
ReflectionUtils.doWithMethods(bean.getClass(), |
||||
new MethodCallback() { |
||||
@Override |
||||
public void doWith(Method method) throws IllegalArgumentException, |
||||
IllegalAccessException { |
||||
add.set(true); |
||||
} |
||||
}, |
||||
new MethodFilter() { |
||||
@Override |
||||
public boolean matches(Method method) { |
||||
for (Class<? extends Annotation> annotationClass : annotations) { |
||||
if (method.isAnnotationPresent(annotationClass)) { |
||||
return true; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
}); |
||||
|
||||
if (add.get()) { |
||||
originalBeanClass = bean.getClass(); |
||||
} |
||||
return bean; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,256 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.handler; |
||||
|
||||
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; |
||||
|
||||
import java.io.IOException; |
||||
import java.net.InetSocketAddress; |
||||
import java.util.HashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.Set; |
||||
import java.util.UUID; |
||||
import java.util.concurrent.TimeUnit; |
||||
|
||||
import com.fr.third.socketio.Configuration; |
||||
import com.fr.third.socketio.Disconnectable; |
||||
import com.fr.third.socketio.DisconnectableHub; |
||||
import com.fr.third.socketio.HandshakeData; |
||||
import com.fr.third.socketio.SocketIOClient; |
||||
import com.fr.third.socketio.Transport; |
||||
import com.fr.third.socketio.messages.HttpErrorMessage; |
||||
import com.fr.third.socketio.namespace.Namespace; |
||||
import com.fr.third.socketio.namespace.NamespacesHub; |
||||
import com.fr.third.socketio.protocol.AuthPacket; |
||||
import com.fr.third.socketio.protocol.Packet; |
||||
import com.fr.third.socketio.protocol.PacketType; |
||||
import com.fr.third.socketio.scheduler.CancelableScheduler; |
||||
import com.fr.third.socketio.scheduler.SchedulerKey; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
import com.fr.third.socketio.ack.AckManager; |
||||
import com.fr.third.socketio.store.StoreFactory; |
||||
import com.fr.third.socketio.store.pubsub.ConnectMessage; |
||||
import com.fr.third.socketio.store.pubsub.PubSubType; |
||||
|
||||
import io.netty.channel.Channel; |
||||
import io.netty.channel.ChannelFutureListener; |
||||
import io.netty.channel.ChannelHandler.Sharable; |
||||
import io.netty.channel.ChannelHandlerContext; |
||||
import io.netty.channel.ChannelInboundHandlerAdapter; |
||||
import io.netty.handler.codec.http.DefaultHttpResponse; |
||||
import io.netty.handler.codec.http.FullHttpRequest; |
||||
import io.netty.handler.codec.http.HttpHeaderNames; |
||||
import io.netty.handler.codec.http.HttpHeaders; |
||||
import io.netty.handler.codec.http.HttpResponse; |
||||
import io.netty.handler.codec.http.HttpResponseStatus; |
||||
import io.netty.handler.codec.http.QueryStringDecoder; |
||||
import io.netty.handler.codec.http.cookie.Cookie; |
||||
import io.netty.handler.codec.http.cookie.ServerCookieDecoder; |
||||
|
||||
@Sharable |
||||
public class AuthorizeHandler extends ChannelInboundHandlerAdapter implements Disconnectable { |
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(AuthorizeHandler.class); |
||||
|
||||
private final CancelableScheduler disconnectScheduler; |
||||
|
||||
private final String connectPath; |
||||
private final Configuration configuration; |
||||
private final NamespacesHub namespacesHub; |
||||
private final StoreFactory storeFactory; |
||||
private final DisconnectableHub disconnectable; |
||||
private final AckManager ackManager; |
||||
private final ClientsBox clientsBox; |
||||
|
||||
public AuthorizeHandler(String connectPath, CancelableScheduler scheduler, Configuration configuration, NamespacesHub namespacesHub, StoreFactory storeFactory, |
||||
DisconnectableHub disconnectable, AckManager ackManager, ClientsBox clientsBox) { |
||||
super(); |
||||
this.connectPath = connectPath; |
||||
this.configuration = configuration; |
||||
this.disconnectScheduler = scheduler; |
||||
this.namespacesHub = namespacesHub; |
||||
this.storeFactory = storeFactory; |
||||
this.disconnectable = disconnectable; |
||||
this.ackManager = ackManager; |
||||
this.clientsBox = clientsBox; |
||||
} |
||||
|
||||
@Override |
||||
public void channelActive(final ChannelHandlerContext ctx) throws Exception { |
||||
SchedulerKey key = new SchedulerKey(SchedulerKey.Type.PING_TIMEOUT, ctx.channel()); |
||||
disconnectScheduler.schedule(key, new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
ctx.channel().close(); |
||||
log.debug("Client with ip {} opened channel but doesn't send any data! Channel closed!", ctx.channel().remoteAddress()); |
||||
} |
||||
}, configuration.getFirstDataTimeout(), TimeUnit.MILLISECONDS); |
||||
super.channelActive(ctx); |
||||
} |
||||
|
||||
@Override |
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { |
||||
SchedulerKey key = new SchedulerKey(SchedulerKey.Type.PING_TIMEOUT, ctx.channel()); |
||||
disconnectScheduler.cancel(key); |
||||
|
||||
if (msg instanceof FullHttpRequest) { |
||||
FullHttpRequest req = (FullHttpRequest) msg; |
||||
Channel channel = ctx.channel(); |
||||
QueryStringDecoder queryDecoder = new QueryStringDecoder(req.uri()); |
||||
|
||||
if (!configuration.isAllowCustomRequests() |
||||
&& !queryDecoder.path().startsWith(connectPath)) { |
||||
HttpResponse res = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.BAD_REQUEST); |
||||
channel.writeAndFlush(res).addListener(ChannelFutureListener.CLOSE); |
||||
req.release(); |
||||
return; |
||||
} |
||||
|
||||
List<String> sid = queryDecoder.parameters().get("sid"); |
||||
if (queryDecoder.path().equals(connectPath) |
||||
&& sid == null) { |
||||
String origin = req.headers().get(HttpHeaderNames.ORIGIN); |
||||
if (!authorize(ctx, channel, origin, queryDecoder.parameters(), req)) { |
||||
req.release(); |
||||
return; |
||||
} |
||||
// forward message to polling or websocket handler to bind channel
|
||||
} |
||||
} |
||||
ctx.fireChannelRead(msg); |
||||
} |
||||
|
||||
private boolean authorize(ChannelHandlerContext ctx, Channel channel, String origin, Map<String, List<String>> params, FullHttpRequest req) |
||||
throws IOException { |
||||
Map<String, List<String>> headers = new HashMap<String, List<String>>(req.headers().names().size()); |
||||
for (String name : req.headers().names()) { |
||||
List<String> values = req.headers().getAll(name); |
||||
headers.put(name, values); |
||||
} |
||||
|
||||
HandshakeData data = new HandshakeData(req.headers(), params, |
||||
(InetSocketAddress)channel.remoteAddress(), |
||||
(InetSocketAddress)channel.localAddress(), |
||||
req.uri(), origin != null && !origin.equalsIgnoreCase("null")); |
||||
|
||||
boolean result = false; |
||||
try { |
||||
result = configuration.getAuthorizationListener().isAuthorized(data); |
||||
} catch (Exception e) { |
||||
log.error("Authorization error", e); |
||||
} |
||||
|
||||
if (!result) { |
||||
HttpResponse res = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.UNAUTHORIZED); |
||||
channel.writeAndFlush(res) |
||||
.addListener(ChannelFutureListener.CLOSE); |
||||
log.debug("Handshake unauthorized, query params: {} headers: {}", params, headers); |
||||
return false; |
||||
} |
||||
|
||||
UUID sessionId = this.generateOrGetSessionIdFromRequest(req.headers()); |
||||
|
||||
List<String> transportValue = params.get("transport"); |
||||
if (transportValue == null) { |
||||
log.error("Got no transports for request {}", req.uri()); |
||||
|
||||
HttpResponse res = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.UNAUTHORIZED); |
||||
channel.writeAndFlush(res).addListener(ChannelFutureListener.CLOSE); |
||||
return false; |
||||
} |
||||
|
||||
Transport transport = Transport.byName(transportValue.get(0)); |
||||
if (!configuration.getTransports().contains(transport)) { |
||||
Map<String, Object> errorData = new HashMap<String, Object>(); |
||||
errorData.put("code", 0); |
||||
errorData.put("message", "Transport unknown"); |
||||
|
||||
channel.attr(EncoderHandler.ORIGIN).set(origin); |
||||
channel.writeAndFlush(new HttpErrorMessage(errorData)); |
||||
return false; |
||||
} |
||||
|
||||
ClientHead client = new ClientHead(sessionId, ackManager, disconnectable, storeFactory, data, clientsBox, transport, disconnectScheduler, configuration); |
||||
channel.attr(ClientHead.CLIENT).set(client); |
||||
clientsBox.addClient(client); |
||||
|
||||
String[] transports = {}; |
||||
if (configuration.getTransports().contains(Transport.WEBSOCKET)) { |
||||
transports = new String[] {"websocket"}; |
||||
} |
||||
|
||||
AuthPacket authPacket = new AuthPacket(sessionId, transports, configuration.getPingInterval(), |
||||
configuration.getPingTimeout()); |
||||
Packet packet = new Packet(PacketType.OPEN); |
||||
packet.setData(authPacket); |
||||
client.send(packet); |
||||
|
||||
client.schedulePingTimeout(); |
||||
log.debug("Handshake authorized for sessionId: {}, query params: {} headers: {}", sessionId, params, headers); |
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
This method will either generate a new random sessionId or will retrieve the value stored |
||||
in the "io" cookie. Failures to parse will cause a logging warning to be generated and a |
||||
random uuid to be generated instead (same as not passing a cookie in the first place). |
||||
*/ |
||||
private UUID generateOrGetSessionIdFromRequest(HttpHeaders headers) { |
||||
for (String cookieHeader: headers.getAll(HttpHeaderNames.COOKIE)) { |
||||
Set<Cookie> cookies = ServerCookieDecoder.LAX.decode(cookieHeader); |
||||
|
||||
for (Cookie cookie : cookies) { |
||||
if (cookie.name().equals("io")) { |
||||
try { |
||||
return UUID.fromString(cookie.value()); |
||||
} catch ( IllegalArgumentException iaex ) { |
||||
log.warn("Malformed UUID received for session! io=" + cookie.value()); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
return UUID.randomUUID(); |
||||
} |
||||
|
||||
public void connect(UUID sessionId) { |
||||
SchedulerKey key = new SchedulerKey(SchedulerKey.Type.PING_TIMEOUT, sessionId); |
||||
disconnectScheduler.cancel(key); |
||||
} |
||||
|
||||
public void connect(ClientHead client) { |
||||
Namespace ns = namespacesHub.get(Namespace.DEFAULT_NAME); |
||||
|
||||
if (!client.getNamespaces().contains(ns)) { |
||||
Packet packet = new Packet(PacketType.MESSAGE); |
||||
packet.setSubType(PacketType.CONNECT); |
||||
client.send(packet); |
||||
|
||||
configuration.getStoreFactory().pubSubStore().publish(PubSubType.CONNECT, new ConnectMessage(client.getSessionId())); |
||||
|
||||
SocketIOClient nsClient = client.addNamespaceClient(ns); |
||||
ns.onConnect(nsClient); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onDisconnect(ClientHead client) { |
||||
clientsBox.removeClient(client.getSessionId()); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,266 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.handler; |
||||
|
||||
import java.net.SocketAddress; |
||||
import java.util.HashMap; |
||||
import java.util.Map; |
||||
import java.util.Map.Entry; |
||||
import java.util.Queue; |
||||
import java.util.Set; |
||||
import java.util.UUID; |
||||
import java.util.concurrent.TimeUnit; |
||||
import java.util.concurrent.atomic.AtomicBoolean; |
||||
|
||||
import com.fr.third.socketio.Configuration; |
||||
import com.fr.third.socketio.DisconnectableHub; |
||||
import com.fr.third.socketio.HandshakeData; |
||||
import com.fr.third.socketio.Transport; |
||||
import com.fr.third.socketio.messages.OutPacketMessage; |
||||
import com.fr.third.socketio.namespace.Namespace; |
||||
import com.fr.third.socketio.protocol.Packet; |
||||
import com.fr.third.socketio.protocol.PacketType; |
||||
import com.fr.third.socketio.scheduler.CancelableScheduler; |
||||
import com.fr.third.socketio.scheduler.SchedulerKey; |
||||
import com.fr.third.socketio.transport.NamespaceClient; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
import com.fr.third.socketio.ack.AckManager; |
||||
import com.fr.third.socketio.store.Store; |
||||
import com.fr.third.socketio.store.StoreFactory; |
||||
|
||||
import io.netty.channel.Channel; |
||||
import io.netty.channel.ChannelFuture; |
||||
import io.netty.channel.ChannelFutureListener; |
||||
import io.netty.handler.codec.http.HttpHeaderNames; |
||||
import io.netty.util.AttributeKey; |
||||
import io.netty.util.internal.PlatformDependent; |
||||
|
||||
public class ClientHead { |
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ClientHead.class); |
||||
|
||||
public static final AttributeKey<ClientHead> CLIENT = AttributeKey.<ClientHead>valueOf("client"); |
||||
|
||||
private final AtomicBoolean disconnected = new AtomicBoolean(); |
||||
private final Map<Namespace, NamespaceClient> namespaceClients = PlatformDependent.newConcurrentHashMap(); |
||||
private final Map<Transport, TransportState> channels = new HashMap<Transport, TransportState>(2); |
||||
private final HandshakeData handshakeData; |
||||
private final UUID sessionId; |
||||
|
||||
private final Store store; |
||||
private final DisconnectableHub disconnectableHub; |
||||
private final AckManager ackManager; |
||||
private ClientsBox clientsBox; |
||||
private final CancelableScheduler disconnectScheduler; |
||||
private final Configuration configuration; |
||||
|
||||
private Packet lastBinaryPacket; |
||||
|
||||
// TODO use lazy set
|
||||
private volatile Transport currentTransport; |
||||
|
||||
public ClientHead(UUID sessionId, AckManager ackManager, DisconnectableHub disconnectable, |
||||
StoreFactory storeFactory, HandshakeData handshakeData, ClientsBox clientsBox, Transport transport, CancelableScheduler disconnectScheduler, |
||||
Configuration configuration) { |
||||
this.sessionId = sessionId; |
||||
this.ackManager = ackManager; |
||||
this.disconnectableHub = disconnectable; |
||||
this.store = storeFactory.createStore(sessionId); |
||||
this.handshakeData = handshakeData; |
||||
this.clientsBox = clientsBox; |
||||
this.currentTransport = transport; |
||||
this.disconnectScheduler = disconnectScheduler; |
||||
this.configuration = configuration; |
||||
|
||||
channels.put(Transport.POLLING, new TransportState()); |
||||
channels.put(Transport.WEBSOCKET, new TransportState()); |
||||
} |
||||
|
||||
public void bindChannel(Channel channel, Transport transport) { |
||||
log.debug("binding channel: {} to transport: {}", channel, transport); |
||||
|
||||
TransportState state = channels.get(transport); |
||||
Channel prevChannel = state.update(channel); |
||||
if (prevChannel != null) { |
||||
clientsBox.remove(prevChannel); |
||||
} |
||||
clientsBox.add(channel, this); |
||||
|
||||
sendPackets(transport, channel); |
||||
} |
||||
|
||||
public String getOrigin() { |
||||
return handshakeData.getHttpHeaders().get(HttpHeaderNames.ORIGIN); |
||||
} |
||||
|
||||
public ChannelFuture send(Packet packet) { |
||||
return send(packet, getCurrentTransport()); |
||||
} |
||||
|
||||
public void cancelPingTimeout() { |
||||
SchedulerKey key = new SchedulerKey(SchedulerKey.Type.PING_TIMEOUT, sessionId); |
||||
disconnectScheduler.cancel(key); |
||||
} |
||||
|
||||
public void schedulePingTimeout() { |
||||
SchedulerKey key = new SchedulerKey(SchedulerKey.Type.PING_TIMEOUT, sessionId); |
||||
disconnectScheduler.schedule(key, new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
ClientHead client = clientsBox.get(sessionId); |
||||
if (client != null) { |
||||
client.onChannelDisconnect(); |
||||
log.debug("{} removed due to ping timeout", sessionId); |
||||
} |
||||
} |
||||
}, configuration.getPingTimeout() + configuration.getPingInterval(), TimeUnit.MILLISECONDS); |
||||
} |
||||
|
||||
public ChannelFuture send(Packet packet, Transport transport) { |
||||
TransportState state = channels.get(transport); |
||||
state.getPacketsQueue().add(packet); |
||||
|
||||
Channel channel = state.getChannel(); |
||||
if (channel == null |
||||
|| (transport == Transport.POLLING && channel.attr(EncoderHandler.WRITE_ONCE).get() != null)) { |
||||
return null; |
||||
} |
||||
return sendPackets(transport, channel); |
||||
} |
||||
|
||||
private ChannelFuture sendPackets(Transport transport, Channel channel) { |
||||
return channel.writeAndFlush(new OutPacketMessage(this, transport)); |
||||
} |
||||
|
||||
public void removeNamespaceClient(NamespaceClient client) { |
||||
namespaceClients.remove(client.getNamespace()); |
||||
if (namespaceClients.isEmpty()) { |
||||
disconnectableHub.onDisconnect(this); |
||||
} |
||||
} |
||||
|
||||
public NamespaceClient getChildClient(Namespace namespace) { |
||||
return namespaceClients.get(namespace); |
||||
} |
||||
|
||||
public NamespaceClient addNamespaceClient(Namespace namespace) { |
||||
NamespaceClient client = new NamespaceClient(this, namespace); |
||||
namespaceClients.put(namespace, client); |
||||
return client; |
||||
} |
||||
|
||||
public Set<Namespace> getNamespaces() { |
||||
return namespaceClients.keySet(); |
||||
} |
||||
|
||||
public boolean isConnected() { |
||||
return !disconnected.get(); |
||||
} |
||||
|
||||
public void onChannelDisconnect() { |
||||
cancelPingTimeout(); |
||||
|
||||
disconnected.set(true); |
||||
for (NamespaceClient client : namespaceClients.values()) { |
||||
client.onDisconnect(); |
||||
} |
||||
for (TransportState state : channels.values()) { |
||||
if (state.getChannel() != null) { |
||||
clientsBox.remove(state.getChannel()); |
||||
} |
||||
} |
||||
} |
||||
|
||||
public HandshakeData getHandshakeData() { |
||||
return handshakeData; |
||||
} |
||||
|
||||
public AckManager getAckManager() { |
||||
return ackManager; |
||||
} |
||||
|
||||
public UUID getSessionId() { |
||||
return sessionId; |
||||
} |
||||
|
||||
public SocketAddress getRemoteAddress() { |
||||
return handshakeData.getAddress(); |
||||
} |
||||
|
||||
public void disconnect() { |
||||
ChannelFuture future = send(new Packet(PacketType.DISCONNECT)); |
||||
future.addListener(ChannelFutureListener.CLOSE); |
||||
|
||||
onChannelDisconnect(); |
||||
} |
||||
|
||||
public boolean isChannelOpen() { |
||||
for (TransportState state : channels.values()) { |
||||
if (state.getChannel() != null |
||||
&& state.getChannel().isActive()) { |
||||
return true; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
public Store getStore() { |
||||
return store; |
||||
} |
||||
|
||||
public boolean isTransportChannel(Channel channel, Transport transport) { |
||||
TransportState state = channels.get(transport); |
||||
if (state.getChannel() == null) { |
||||
return false; |
||||
} |
||||
return state.getChannel().equals(channel); |
||||
} |
||||
|
||||
public void upgradeCurrentTransport(Transport currentTransport) { |
||||
TransportState state = channels.get(currentTransport); |
||||
|
||||
for (Entry<Transport, TransportState> entry : channels.entrySet()) { |
||||
if (!entry.getKey().equals(currentTransport)) { |
||||
|
||||
Queue<Packet> queue = entry.getValue().getPacketsQueue(); |
||||
state.setPacketsQueue(queue); |
||||
|
||||
sendPackets(currentTransport, state.getChannel()); |
||||
this.currentTransport = currentTransport; |
||||
log.debug("Transport upgraded to: {} for: {}", currentTransport, sessionId); |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
public Transport getCurrentTransport() { |
||||
return currentTransport; |
||||
} |
||||
|
||||
public Queue<Packet> getPacketsQueue(Transport transport) { |
||||
return channels.get(transport).getPacketsQueue(); |
||||
} |
||||
|
||||
public void setLastBinaryPacket(Packet lastBinaryPacket) { |
||||
this.lastBinaryPacket = lastBinaryPacket; |
||||
} |
||||
public Packet getLastBinaryPacket() { |
||||
return lastBinaryPacket; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,65 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.handler; |
||||
|
||||
import com.fr.third.socketio.HandshakeData; |
||||
import io.netty.channel.Channel; |
||||
import io.netty.util.internal.PlatformDependent; |
||||
|
||||
import java.util.Map; |
||||
import java.util.UUID; |
||||
|
||||
public class ClientsBox { |
||||
|
||||
private final Map<UUID, ClientHead> uuid2clients = PlatformDependent.newConcurrentHashMap(); |
||||
private final Map<Channel, ClientHead> channel2clients = PlatformDependent.newConcurrentHashMap(); |
||||
|
||||
// TODO use storeFactory
|
||||
public HandshakeData getHandshakeData(UUID sessionId) { |
||||
ClientHead client = uuid2clients.get(sessionId); |
||||
if (client == null) { |
||||
return null; |
||||
} |
||||
|
||||
return client.getHandshakeData(); |
||||
} |
||||
|
||||
public void addClient(ClientHead clientHead) { |
||||
uuid2clients.put(clientHead.getSessionId(), clientHead); |
||||
} |
||||
|
||||
public void removeClient(UUID sessionId) { |
||||
uuid2clients.remove(sessionId); |
||||
} |
||||
|
||||
public ClientHead get(UUID sessionId) { |
||||
return uuid2clients.get(sessionId); |
||||
} |
||||
|
||||
public void add(Channel channel, ClientHead clientHead) { |
||||
channel2clients.put(channel, clientHead); |
||||
} |
||||
|
||||
public void remove(Channel channel) { |
||||
channel2clients.remove(channel); |
||||
} |
||||
|
||||
|
||||
public ClientHead get(Channel channel) { |
||||
return channel2clients.get(channel); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,346 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.handler; |
||||
|
||||
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; |
||||
|
||||
import java.io.IOException; |
||||
import java.net.URL; |
||||
import java.util.ArrayList; |
||||
import java.util.Enumeration; |
||||
import java.util.List; |
||||
import java.util.Queue; |
||||
import java.util.jar.Attributes; |
||||
import java.util.jar.Manifest; |
||||
|
||||
import com.fr.third.socketio.Configuration; |
||||
import com.fr.third.socketio.Transport; |
||||
import com.fr.third.socketio.messages.HttpErrorMessage; |
||||
import com.fr.third.socketio.messages.HttpMessage; |
||||
import com.fr.third.socketio.messages.OutPacketMessage; |
||||
import com.fr.third.socketio.messages.XHROptionsMessage; |
||||
import com.fr.third.socketio.messages.XHRPostMessage; |
||||
import com.fr.third.socketio.protocol.Packet; |
||||
import com.fr.third.socketio.protocol.PacketEncoder; |
||||
import io.netty.util.concurrent.Future; |
||||
import io.netty.util.concurrent.GenericFutureListener; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
import io.netty.buffer.ByteBuf; |
||||
import io.netty.buffer.ByteBufOutputStream; |
||||
import io.netty.buffer.ByteBufUtil; |
||||
import io.netty.channel.Channel; |
||||
import io.netty.channel.ChannelFuture; |
||||
import io.netty.channel.ChannelFutureListener; |
||||
import io.netty.channel.ChannelHandler.Sharable; |
||||
import io.netty.channel.ChannelHandlerContext; |
||||
import io.netty.channel.ChannelOutboundHandlerAdapter; |
||||
import io.netty.channel.ChannelPromise; |
||||
import io.netty.handler.codec.http.DefaultHttpContent; |
||||
import io.netty.handler.codec.http.DefaultHttpResponse; |
||||
import io.netty.handler.codec.http.HttpHeaderNames; |
||||
import io.netty.handler.codec.http.HttpHeaderValues; |
||||
import io.netty.handler.codec.http.HttpResponse; |
||||
import io.netty.handler.codec.http.HttpResponseStatus; |
||||
import io.netty.handler.codec.http.HttpUtil; |
||||
import io.netty.handler.codec.http.LastHttpContent; |
||||
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; |
||||
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; |
||||
import io.netty.handler.codec.http.websocketx.WebSocketFrame; |
||||
import io.netty.util.Attribute; |
||||
import io.netty.util.AttributeKey; |
||||
import io.netty.util.CharsetUtil; |
||||
|
||||
@Sharable |
||||
public class EncoderHandler extends ChannelOutboundHandlerAdapter { |
||||
|
||||
private static final byte[] OK = "ok".getBytes(CharsetUtil.UTF_8); |
||||
|
||||
public static final AttributeKey<String> ORIGIN = AttributeKey.valueOf("origin"); |
||||
public static final AttributeKey<String> USER_AGENT = AttributeKey.valueOf("userAgent"); |
||||
public static final AttributeKey<Boolean> B64 = AttributeKey.valueOf("b64"); |
||||
public static final AttributeKey<Integer> JSONP_INDEX = AttributeKey.valueOf("jsonpIndex"); |
||||
public static final AttributeKey<Boolean> WRITE_ONCE = AttributeKey.valueOf("writeOnce"); |
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(EncoderHandler.class); |
||||
|
||||
private final PacketEncoder encoder; |
||||
|
||||
private String version; |
||||
private Configuration configuration; |
||||
|
||||
public EncoderHandler(Configuration configuration, PacketEncoder encoder) throws IOException { |
||||
this.encoder = encoder; |
||||
this.configuration = configuration; |
||||
|
||||
if (configuration.isAddVersionHeader()) { |
||||
readVersion(); |
||||
} |
||||
} |
||||
|
||||
private void readVersion() throws IOException { |
||||
Enumeration<URL> resources = getClass().getClassLoader().getResources("META-INF/MANIFEST.MF"); |
||||
while (resources.hasMoreElements()) { |
||||
try { |
||||
Manifest manifest = new Manifest(resources.nextElement().openStream()); |
||||
Attributes attrs = manifest.getMainAttributes(); |
||||
if (attrs == null) { |
||||
continue; |
||||
} |
||||
String name = attrs.getValue("Bundle-Name"); |
||||
if (name != null && name.equals("netty-socketio")) { |
||||
version = name + "/" + attrs.getValue("Bundle-Version"); |
||||
break; |
||||
} |
||||
} catch (IOException E) { |
||||
// skip it
|
||||
} |
||||
} |
||||
} |
||||
|
||||
private void write(XHROptionsMessage msg, ChannelHandlerContext ctx, ChannelPromise promise) { |
||||
HttpResponse res = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.OK); |
||||
|
||||
res.headers().add(HttpHeaderNames.SET_COOKIE, "io=" + msg.getSessionId()) |
||||
.add(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE) |
||||
.add(HttpHeaderNames.ACCESS_CONTROL_ALLOW_HEADERS, HttpHeaderNames.CONTENT_TYPE); |
||||
|
||||
String origin = ctx.channel().attr(ORIGIN).get(); |
||||
addOriginHeaders(origin, res); |
||||
|
||||
ByteBuf out = encoder.allocateBuffer(ctx.alloc()); |
||||
sendMessage(msg, ctx.channel(), out, res, promise); |
||||
} |
||||
|
||||
private void write(XHRPostMessage msg, ChannelHandlerContext ctx, ChannelPromise promise) { |
||||
ByteBuf out = encoder.allocateBuffer(ctx.alloc()); |
||||
out.writeBytes(OK); |
||||
sendMessage(msg, ctx.channel(), out, "text/html", promise, HttpResponseStatus.OK); |
||||
} |
||||
|
||||
private void sendMessage(HttpMessage msg, Channel channel, ByteBuf out, String type, ChannelPromise promise, HttpResponseStatus status) { |
||||
HttpResponse res = new DefaultHttpResponse(HTTP_1_1, status); |
||||
|
||||
res.headers().add(HttpHeaderNames.CONTENT_TYPE, type) |
||||
.add(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE); |
||||
if (msg.getSessionId() != null) { |
||||
res.headers().add(HttpHeaderNames.SET_COOKIE, "io=" + msg.getSessionId()); |
||||
} |
||||
|
||||
String origin = channel.attr(ORIGIN).get(); |
||||
addOriginHeaders(origin, res); |
||||
|
||||
HttpUtil.setContentLength(res, out.readableBytes()); |
||||
|
||||
// prevent XSS warnings on IE
|
||||
// https://github.com/LearnBoost/socket.io/pull/1333
|
||||
String userAgent = channel.attr(EncoderHandler.USER_AGENT).get(); |
||||
if (userAgent != null && (userAgent.contains(";MSIE") || userAgent.contains("Trident/"))) { |
||||
res.headers().add("X-XSS-Protection", "0"); |
||||
} |
||||
|
||||
sendMessage(msg, channel, out, res, promise); |
||||
} |
||||
|
||||
private void sendMessage(HttpMessage msg, Channel channel, ByteBuf out, HttpResponse res, ChannelPromise promise) { |
||||
channel.write(res); |
||||
|
||||
if (log.isTraceEnabled()) { |
||||
if (msg.getSessionId() != null) { |
||||
log.trace("Out message: {} - sessionId: {}", out.toString(CharsetUtil.UTF_8), msg.getSessionId()); |
||||
} else { |
||||
log.trace("Out message: {}", out.toString(CharsetUtil.UTF_8)); |
||||
} |
||||
} |
||||
|
||||
if (out.isReadable()) { |
||||
channel.write(new DefaultHttpContent(out)); |
||||
} else { |
||||
out.release(); |
||||
} |
||||
|
||||
channel.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT, promise).addListener(ChannelFutureListener.CLOSE); |
||||
} |
||||
|
||||
private void sendError(HttpErrorMessage errorMsg, ChannelHandlerContext ctx, ChannelPromise promise) throws IOException { |
||||
final ByteBuf encBuf = encoder.allocateBuffer(ctx.alloc()); |
||||
ByteBufOutputStream out = new ByteBufOutputStream(encBuf); |
||||
encoder.getJsonSupport().writeValue(out, errorMsg.getData()); |
||||
|
||||
sendMessage(errorMsg, ctx.channel(), encBuf, "application/json", promise, HttpResponseStatus.BAD_REQUEST); |
||||
} |
||||
|
||||
private void addOriginHeaders(String origin, HttpResponse res) { |
||||
if (version != null) { |
||||
res.headers().add(HttpHeaderNames.SERVER, version); |
||||
} |
||||
|
||||
if (configuration.getOrigin() != null) { |
||||
res.headers().add(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN, configuration.getOrigin()); |
||||
res.headers().add(HttpHeaderNames.ACCESS_CONTROL_ALLOW_CREDENTIALS, Boolean.TRUE); |
||||
} else { |
||||
if (origin != null) { |
||||
res.headers().add(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN, origin); |
||||
res.headers().add(HttpHeaderNames.ACCESS_CONTROL_ALLOW_CREDENTIALS, Boolean.TRUE); |
||||
} else { |
||||
res.headers().add(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN, "*"); |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { |
||||
if (!(msg instanceof HttpMessage)) { |
||||
super.write(ctx, msg, promise); |
||||
return; |
||||
} |
||||
|
||||
if (msg instanceof OutPacketMessage) { |
||||
OutPacketMessage m = (OutPacketMessage) msg; |
||||
if (m.getTransport() == Transport.WEBSOCKET) { |
||||
handleWebsocket((OutPacketMessage) msg, ctx, promise); |
||||
} |
||||
if (m.getTransport() == Transport.POLLING) { |
||||
handleHTTP((OutPacketMessage) msg, ctx, promise); |
||||
} |
||||
} else if (msg instanceof XHROptionsMessage) { |
||||
write((XHROptionsMessage) msg, ctx, promise); |
||||
} else if (msg instanceof XHRPostMessage) { |
||||
write((XHRPostMessage) msg, ctx, promise); |
||||
} else if (msg instanceof HttpErrorMessage) { |
||||
sendError((HttpErrorMessage) msg, ctx, promise); |
||||
} |
||||
} |
||||
|
||||
private void handleWebsocket(final OutPacketMessage msg, ChannelHandlerContext ctx, ChannelPromise promise) throws IOException { |
||||
ChannelFutureList writeFutureList = new ChannelFutureList(); |
||||
|
||||
while (true) { |
||||
Queue<Packet> queue = msg.getClientHead().getPacketsQueue(msg.getTransport()); |
||||
Packet packet = queue.poll(); |
||||
if (packet == null) { |
||||
writeFutureList.setChannelPromise(promise); |
||||
break; |
||||
} |
||||
|
||||
final ByteBuf out = encoder.allocateBuffer(ctx.alloc()); |
||||
encoder.encodePacket(packet, out, ctx.alloc(), true); |
||||
|
||||
WebSocketFrame res = new TextWebSocketFrame(out); |
||||
if (log.isTraceEnabled()) { |
||||
log.trace("Out message: {} sessionId: {}", out.toString(CharsetUtil.UTF_8), msg.getSessionId()); |
||||
} |
||||
|
||||
if (out.isReadable()) { |
||||
writeFutureList.add(ctx.channel().writeAndFlush(res)); |
||||
} else { |
||||
out.release(); |
||||
} |
||||
|
||||
for (ByteBuf buf : packet.getAttachments()) { |
||||
ByteBuf outBuf = encoder.allocateBuffer(ctx.alloc()); |
||||
outBuf.writeByte(4); |
||||
outBuf.writeBytes(buf); |
||||
if (log.isTraceEnabled()) { |
||||
log.trace("Out attachment: {} sessionId: {}", ByteBufUtil.hexDump(outBuf), msg.getSessionId()); |
||||
} |
||||
writeFutureList.add(ctx.channel().writeAndFlush(new BinaryWebSocketFrame(outBuf))); |
||||
} |
||||
} |
||||
} |
||||
|
||||
private void handleHTTP(OutPacketMessage msg, ChannelHandlerContext ctx, ChannelPromise promise) throws IOException { |
||||
Channel channel = ctx.channel(); |
||||
Attribute<Boolean> attr = channel.attr(WRITE_ONCE); |
||||
|
||||
Queue<Packet> queue = msg.getClientHead().getPacketsQueue(msg.getTransport()); |
||||
|
||||
if (!channel.isActive() || queue.isEmpty() || !attr.compareAndSet(null, true)) { |
||||
promise.trySuccess(); |
||||
return; |
||||
} |
||||
|
||||
ByteBuf out = encoder.allocateBuffer(ctx.alloc()); |
||||
Boolean b64 = ctx.channel().attr(EncoderHandler.B64).get(); |
||||
if (b64 != null && b64) { |
||||
Integer jsonpIndex = ctx.channel().attr(EncoderHandler.JSONP_INDEX).get(); |
||||
encoder.encodeJsonP(jsonpIndex, queue, out, ctx.alloc(), 50); |
||||
String type = "application/javascript"; |
||||
if (jsonpIndex == null) { |
||||
type = "text/plain"; |
||||
} |
||||
sendMessage(msg, channel, out, type, promise, HttpResponseStatus.OK); |
||||
} else { |
||||
encoder.encodePackets(queue, out, ctx.alloc(), 50); |
||||
sendMessage(msg, channel, out, "application/octet-stream", promise, HttpResponseStatus.OK); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Helper class for the handleWebsocket method, handles a list of ChannelFutures and |
||||
* sets the status of a promise when |
||||
* - any of the operations fail |
||||
* - all of the operations succeed |
||||
* The setChannelPromise method should be called after all the futures are added |
||||
*/ |
||||
private class ChannelFutureList implements GenericFutureListener<Future<Void>> { |
||||
|
||||
private List<ChannelFuture> futureList = new ArrayList<ChannelFuture>(); |
||||
private ChannelPromise promise = null; |
||||
|
||||
private void cleanup() { |
||||
promise = null; |
||||
for (ChannelFuture f : futureList) f.removeListener(this); |
||||
} |
||||
|
||||
private void validate() { |
||||
boolean allSuccess = true; |
||||
for (ChannelFuture f : futureList) { |
||||
if (f.isDone()) { |
||||
if (!f.isSuccess()) { |
||||
promise.tryFailure(f.cause()); |
||||
cleanup(); |
||||
return; |
||||
} |
||||
} |
||||
else { |
||||
allSuccess = false; |
||||
} |
||||
} |
||||
if (allSuccess) { |
||||
promise.trySuccess(); |
||||
cleanup(); |
||||
} |
||||
} |
||||
|
||||
public void add(ChannelFuture f) { |
||||
futureList.add(f); |
||||
f.addListener(this); |
||||
} |
||||
|
||||
public void setChannelPromise(ChannelPromise p) { |
||||
promise = p; |
||||
validate(); |
||||
} |
||||
|
||||
@Override |
||||
public void operationComplete(Future<Void> voidFuture) throws Exception { |
||||
if (promise != null) validate(); |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,107 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.handler; |
||||
|
||||
import com.fr.third.socketio.listener.ExceptionListener; |
||||
import com.fr.third.socketio.messages.PacketsMessage; |
||||
import com.fr.third.socketio.namespace.Namespace; |
||||
import com.fr.third.socketio.namespace.NamespacesHub; |
||||
import com.fr.third.socketio.protocol.Packet; |
||||
import com.fr.third.socketio.protocol.PacketDecoder; |
||||
import com.fr.third.socketio.protocol.PacketType; |
||||
import com.fr.third.socketio.transport.NamespaceClient; |
||||
import io.netty.buffer.ByteBuf; |
||||
import io.netty.channel.ChannelHandler.Sharable; |
||||
import io.netty.channel.ChannelHandlerContext; |
||||
import io.netty.channel.SimpleChannelInboundHandler; |
||||
import io.netty.util.CharsetUtil; |
||||
|
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
@Sharable |
||||
public class InPacketHandler extends SimpleChannelInboundHandler<PacketsMessage> { |
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(InPacketHandler.class); |
||||
|
||||
private final PacketListener packetListener; |
||||
private final PacketDecoder decoder; |
||||
private final NamespacesHub namespacesHub; |
||||
private final ExceptionListener exceptionListener; |
||||
|
||||
public InPacketHandler(PacketListener packetListener, PacketDecoder decoder, NamespacesHub namespacesHub, ExceptionListener exceptionListener) { |
||||
super(); |
||||
this.packetListener = packetListener; |
||||
this.decoder = decoder; |
||||
this.namespacesHub = namespacesHub; |
||||
this.exceptionListener = exceptionListener; |
||||
} |
||||
|
||||
@Override |
||||
protected void channelRead0(io.netty.channel.ChannelHandlerContext ctx, PacketsMessage message) |
||||
throws Exception { |
||||
ByteBuf content = message.getContent(); |
||||
ClientHead client = message.getClient(); |
||||
|
||||
if (log.isTraceEnabled()) { |
||||
log.trace("In message: {} sessionId: {}", content.toString(CharsetUtil.UTF_8), client.getSessionId()); |
||||
} |
||||
while (content.isReadable()) { |
||||
try { |
||||
Packet packet = decoder.decodePackets(content, client); |
||||
if (packet.hasAttachments() && !packet.isAttachmentsLoaded()) { |
||||
return; |
||||
} |
||||
Namespace ns = namespacesHub.get(packet.getNsp()); |
||||
if (ns == null) { |
||||
if (packet.getSubType() == PacketType.CONNECT) { |
||||
Packet p = new Packet(PacketType.MESSAGE); |
||||
p.setSubType(PacketType.ERROR); |
||||
p.setNsp(packet.getNsp()); |
||||
p.setData("Invalid namespace"); |
||||
client.send(p); |
||||
return; |
||||
} |
||||
log.debug("Can't find namespace for endpoint: {}, sessionId: {} probably it was removed.", packet.getNsp(), client.getSessionId()); |
||||
return; |
||||
} |
||||
|
||||
if (packet.getSubType() == PacketType.CONNECT) { |
||||
client.addNamespaceClient(ns); |
||||
} |
||||
|
||||
NamespaceClient nClient = client.getChildClient(ns); |
||||
if (nClient == null) { |
||||
log.debug("Can't find namespace client in namespace: {}, sessionId: {} probably it was disconnected.", ns.getName(), client.getSessionId()); |
||||
return; |
||||
} |
||||
packetListener.onPacket(packet, nClient, message.getTransport()); |
||||
} catch (Exception ex) { |
||||
String c = content.toString(CharsetUtil.UTF_8); |
||||
log.error("Error during data processing. Client sessionId: " + client.getSessionId() + ", data: " + c, ex); |
||||
throw ex; |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable e) throws Exception { |
||||
if (!exceptionListener.exceptionCaught(ctx, e)) { |
||||
super.exceptionCaught(ctx, e); |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,120 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.handler; |
||||
|
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
|
||||
import com.fr.third.socketio.AckRequest; |
||||
import com.fr.third.socketio.Transport; |
||||
import com.fr.third.socketio.ack.AckManager; |
||||
import com.fr.third.socketio.namespace.Namespace; |
||||
import com.fr.third.socketio.namespace.NamespacesHub; |
||||
import com.fr.third.socketio.protocol.Packet; |
||||
import com.fr.third.socketio.protocol.PacketType; |
||||
import com.fr.third.socketio.scheduler.CancelableScheduler; |
||||
import com.fr.third.socketio.scheduler.SchedulerKey; |
||||
import com.fr.third.socketio.transport.NamespaceClient; |
||||
import com.fr.third.socketio.transport.PollingTransport; |
||||
|
||||
public class PacketListener { |
||||
|
||||
private final NamespacesHub namespacesHub; |
||||
private final AckManager ackManager; |
||||
private final CancelableScheduler scheduler; |
||||
|
||||
public PacketListener(AckManager ackManager, NamespacesHub namespacesHub, PollingTransport xhrPollingTransport, |
||||
CancelableScheduler scheduler) { |
||||
this.ackManager = ackManager; |
||||
this.namespacesHub = namespacesHub; |
||||
this.scheduler = scheduler; |
||||
} |
||||
|
||||
public void onPacket(Packet packet, NamespaceClient client, Transport transport) { |
||||
final AckRequest ackRequest = new AckRequest(packet, client); |
||||
|
||||
if (packet.isAckRequested()) { |
||||
ackManager.initAckIndex(client.getSessionId(), packet.getAckId()); |
||||
} |
||||
|
||||
switch (packet.getType()) { |
||||
case PING: { |
||||
Packet outPacket = new Packet(PacketType.PONG); |
||||
outPacket.setData(packet.getData()); |
||||
// TODO use future
|
||||
client.getBaseClient().send(outPacket, transport); |
||||
|
||||
if ("probe".equals(packet.getData())) { |
||||
client.getBaseClient().send(new Packet(PacketType.NOOP), Transport.POLLING); |
||||
} else { |
||||
client.getBaseClient().schedulePingTimeout(); |
||||
} |
||||
Namespace namespace = namespacesHub.get(packet.getNsp()); |
||||
namespace.onPing(client); |
||||
break; |
||||
} |
||||
|
||||
case UPGRADE: { |
||||
client.getBaseClient().schedulePingTimeout(); |
||||
|
||||
SchedulerKey key = new SchedulerKey(SchedulerKey.Type.UPGRADE_TIMEOUT, client.getSessionId()); |
||||
scheduler.cancel(key); |
||||
|
||||
client.getBaseClient().upgradeCurrentTransport(transport); |
||||
break; |
||||
} |
||||
|
||||
case MESSAGE: { |
||||
client.getBaseClient().schedulePingTimeout(); |
||||
|
||||
if (packet.getSubType() == PacketType.DISCONNECT) { |
||||
client.onDisconnect(); |
||||
} |
||||
|
||||
if (packet.getSubType() == PacketType.CONNECT) { |
||||
Namespace namespace = namespacesHub.get(packet.getNsp()); |
||||
namespace.onConnect(client); |
||||
// send connect handshake packet back to client
|
||||
client.getBaseClient().send(packet, transport); |
||||
} |
||||
|
||||
if (packet.getSubType() == PacketType.ACK |
||||
|| packet.getSubType() == PacketType.BINARY_ACK) { |
||||
ackManager.onAck(client, packet); |
||||
} |
||||
|
||||
if (packet.getSubType() == PacketType.EVENT |
||||
|| packet.getSubType() == PacketType.BINARY_EVENT) { |
||||
Namespace namespace = namespacesHub.get(packet.getNsp()); |
||||
List<Object> args = Collections.emptyList(); |
||||
if (packet.getData() != null) { |
||||
args = packet.getData(); |
||||
} |
||||
namespace.onEvent(client, packet.getName(), args, ackRequest); |
||||
} |
||||
break; |
||||
} |
||||
|
||||
case CLOSE: |
||||
client.getBaseClient().onChannelDisconnect(); |
||||
break; |
||||
|
||||
default: |
||||
break; |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,34 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.handler; |
||||
|
||||
public class SocketIOException extends RuntimeException { |
||||
|
||||
private static final long serialVersionUID = -9218908839842557188L; |
||||
|
||||
public SocketIOException(String message, Throwable cause) { |
||||
super(message, cause); |
||||
} |
||||
|
||||
public SocketIOException(String message) { |
||||
super(message); |
||||
} |
||||
|
||||
public SocketIOException(Throwable cause) { |
||||
super(cause); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,28 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.handler; |
||||
|
||||
import com.fr.third.socketio.AuthorizationListener; |
||||
import com.fr.third.socketio.HandshakeData; |
||||
|
||||
public class SuccessAuthorizationListener implements AuthorizationListener { |
||||
|
||||
@Override |
||||
public boolean isAuthorized(HandshakeData data) { |
||||
return true; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,48 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.handler; |
||||
|
||||
import java.util.Queue; |
||||
import java.util.concurrent.ConcurrentLinkedQueue; |
||||
|
||||
import com.fr.third.socketio.protocol.Packet; |
||||
|
||||
import io.netty.channel.Channel; |
||||
|
||||
public class TransportState { |
||||
|
||||
private Queue<Packet> packetsQueue = new ConcurrentLinkedQueue<Packet>(); |
||||
private Channel channel; |
||||
|
||||
public void setPacketsQueue(Queue<Packet> packetsQueue) { |
||||
this.packetsQueue = packetsQueue; |
||||
} |
||||
|
||||
public Queue<Packet> getPacketsQueue() { |
||||
return packetsQueue; |
||||
} |
||||
|
||||
public Channel getChannel() { |
||||
return channel; |
||||
} |
||||
|
||||
public Channel update(Channel channel) { |
||||
Channel prevChannel = this.channel; |
||||
this.channel = channel; |
||||
return prevChannel; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,57 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.handler; |
||||
|
||||
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; |
||||
|
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
import io.netty.channel.Channel; |
||||
import io.netty.channel.ChannelFuture; |
||||
import io.netty.channel.ChannelFutureListener; |
||||
import io.netty.channel.ChannelHandler.Sharable; |
||||
import io.netty.channel.ChannelHandlerContext; |
||||
import io.netty.channel.ChannelInboundHandlerAdapter; |
||||
import io.netty.handler.codec.http.DefaultHttpResponse; |
||||
import io.netty.handler.codec.http.FullHttpRequest; |
||||
import io.netty.handler.codec.http.HttpResponse; |
||||
import io.netty.handler.codec.http.HttpResponseStatus; |
||||
import io.netty.handler.codec.http.QueryStringDecoder; |
||||
|
||||
@Sharable |
||||
public class WrongUrlHandler extends ChannelInboundHandlerAdapter { |
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(WrongUrlHandler.class); |
||||
|
||||
@Override |
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { |
||||
if (msg instanceof FullHttpRequest) { |
||||
FullHttpRequest req = (FullHttpRequest) msg; |
||||
Channel channel = ctx.channel(); |
||||
QueryStringDecoder queryDecoder = new QueryStringDecoder(req.uri()); |
||||
|
||||
HttpResponse res = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.BAD_REQUEST); |
||||
ChannelFuture f = channel.writeAndFlush(res); |
||||
f.addListener(ChannelFutureListener.CLOSE); |
||||
req.release(); |
||||
log.warn("Blocked wrong socket.io-context request! url: {}, params: {}, ip: {}", queryDecoder.path(), queryDecoder.parameters(), channel.remoteAddress()); |
||||
return; |
||||
} |
||||
super.channelRead(ctx, msg); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,36 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.listener; |
||||
|
||||
public interface ClientListeners { |
||||
|
||||
void addMultiTypeEventListener(String eventName, MultiTypeEventListener listener, Class<?> ... eventClass); |
||||
|
||||
<T> void addEventListener(String eventName, Class<T> eventClass, DataListener<T> listener); |
||||
|
||||
void addDisconnectListener(DisconnectListener listener); |
||||
|
||||
void addConnectListener(ConnectListener listener); |
||||
|
||||
void addPingListener(PingListener listener); |
||||
|
||||
void addListeners(Object listeners); |
||||
|
||||
void addListeners(Object listeners, Class<?> listenersClass); |
||||
|
||||
void removeAllListeners(String eventName); |
||||
|
||||
} |
@ -0,0 +1,24 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.listener; |
||||
|
||||
import com.fr.third.socketio.SocketIOClient; |
||||
|
||||
public interface ConnectListener { |
||||
|
||||
void onConnect(SocketIOClient client); |
||||
|
||||
} |
@ -0,0 +1,34 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.listener; |
||||
|
||||
import com.fr.third.socketio.AckRequest; |
||||
import com.fr.third.socketio.SocketIOClient; |
||||
|
||||
public interface DataListener<T> { |
||||
|
||||
/** |
||||
* Invokes when data object received from client |
||||
* |
||||
* @param client - receiver |
||||
* @param data - received object |
||||
* @param ackSender - ack request |
||||
* |
||||
* @throws Exception |
||||
*/ |
||||
void onData(SocketIOClient client, T data, AckRequest ackSender) throws Exception; |
||||
|
||||
} |
@ -0,0 +1,56 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.listener; |
||||
|
||||
import com.fr.third.socketio.SocketIOClient; |
||||
import io.netty.channel.ChannelHandlerContext; |
||||
|
||||
import java.util.List; |
||||
|
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
public class DefaultExceptionListener extends ExceptionListenerAdapter { |
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(DefaultExceptionListener.class); |
||||
|
||||
@Override |
||||
public void onEventException(Exception e, List<Object> args, SocketIOClient client) { |
||||
log.error(e.getMessage(), e); |
||||
} |
||||
|
||||
@Override |
||||
public void onDisconnectException(Exception e, SocketIOClient client) { |
||||
log.error(e.getMessage(), e); |
||||
} |
||||
|
||||
@Override |
||||
public void onConnectException(Exception e, SocketIOClient client) { |
||||
log.error(e.getMessage(), e); |
||||
} |
||||
|
||||
@Override |
||||
public void onPingException(Exception e, SocketIOClient client) { |
||||
log.error(e.getMessage(), e); |
||||
} |
||||
|
||||
@Override |
||||
public boolean exceptionCaught(ChannelHandlerContext ctx, Throwable e) throws Exception { |
||||
log.error(e.getMessage(), e); |
||||
return true; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,24 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.listener; |
||||
|
||||
import com.fr.third.socketio.SocketIOClient; |
||||
|
||||
public interface DisconnectListener { |
||||
|
||||
void onDisconnect(SocketIOClient client); |
||||
|
||||
} |
@ -0,0 +1,35 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.listener; |
||||
|
||||
import com.fr.third.socketio.SocketIOClient; |
||||
import io.netty.channel.ChannelHandlerContext; |
||||
|
||||
import java.util.List; |
||||
|
||||
public interface ExceptionListener { |
||||
|
||||
void onEventException(Exception e, List<Object> args, SocketIOClient client); |
||||
|
||||
void onDisconnectException(Exception e, SocketIOClient client); |
||||
|
||||
void onConnectException(Exception e, SocketIOClient client); |
||||
|
||||
void onPingException(Exception e, SocketIOClient client); |
||||
|
||||
boolean exceptionCaught(ChannelHandlerContext ctx, Throwable e) throws Exception; |
||||
|
||||
} |
@ -0,0 +1,47 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.listener; |
||||
|
||||
import com.fr.third.socketio.SocketIOClient; |
||||
import io.netty.channel.ChannelHandlerContext; |
||||
|
||||
import java.util.List; |
||||
|
||||
/** |
||||
* Base callback exceptions listener |
||||
* |
||||
* |
||||
*/ |
||||
public abstract class ExceptionListenerAdapter implements ExceptionListener { |
||||
|
||||
@Override |
||||
public void onEventException(Exception e, List<Object> data, SocketIOClient client) { |
||||
} |
||||
|
||||
@Override |
||||
public void onDisconnectException(Exception e, SocketIOClient client) { |
||||
} |
||||
|
||||
@Override |
||||
public void onConnectException(Exception e, SocketIOClient client) { |
||||
} |
||||
|
||||
@Override |
||||
public boolean exceptionCaught(ChannelHandlerContext ctx, Throwable e) throws Exception { |
||||
return false; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,26 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.listener; |
||||
|
||||
import com.fr.third.socketio.MultiTypeArgs; |
||||
|
||||
/** |
||||
* Multi type args event listener |
||||
* |
||||
*/ |
||||
public interface MultiTypeEventListener extends DataListener<MultiTypeArgs> { |
||||
|
||||
} |
@ -0,0 +1,24 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.listener; |
||||
|
||||
import com.fr.third.socketio.SocketIOClient; |
||||
|
||||
public interface PingListener { |
||||
|
||||
void onPing(SocketIOClient client); |
||||
|
||||
} |
@ -0,0 +1,33 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.messages; |
||||
|
||||
import java.util.Map; |
||||
|
||||
public class HttpErrorMessage extends HttpMessage { |
||||
|
||||
private final Map<String, Object> data; |
||||
|
||||
public HttpErrorMessage(Map<String, Object> data) { |
||||
super(null, null); |
||||
this.data = data; |
||||
} |
||||
|
||||
public Map<String, Object> getData() { |
||||
return data; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,38 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.messages; |
||||
|
||||
import java.util.UUID; |
||||
|
||||
public abstract class HttpMessage { |
||||
|
||||
private final String origin; |
||||
private final UUID sessionId; |
||||
|
||||
public HttpMessage(String origin, UUID sessionId) { |
||||
this.origin = origin; |
||||
this.sessionId = sessionId; |
||||
} |
||||
|
||||
public String getOrigin() { |
||||
return origin; |
||||
} |
||||
|
||||
public UUID getSessionId() { |
||||
return sessionId; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,41 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.messages; |
||||
|
||||
import com.fr.third.socketio.Transport; |
||||
import com.fr.third.socketio.handler.ClientHead; |
||||
|
||||
public class OutPacketMessage extends HttpMessage { |
||||
|
||||
private final ClientHead clientHead; |
||||
private final Transport transport; |
||||
|
||||
public OutPacketMessage(ClientHead clientHead, Transport transport) { |
||||
super(clientHead.getOrigin(), clientHead.getSessionId()); |
||||
|
||||
this.clientHead = clientHead; |
||||
this.transport = transport; |
||||
} |
||||
|
||||
public Transport getTransport() { |
||||
return transport; |
||||
} |
||||
|
||||
public ClientHead getClientHead() { |
||||
return clientHead; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,47 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.messages; |
||||
|
||||
import com.fr.third.socketio.Transport; |
||||
import io.netty.buffer.ByteBuf; |
||||
|
||||
import com.fr.third.socketio.handler.ClientHead; |
||||
|
||||
public class PacketsMessage { |
||||
|
||||
private final ClientHead client; |
||||
private final ByteBuf content; |
||||
private final Transport transport; |
||||
|
||||
public PacketsMessage(ClientHead client, ByteBuf content, Transport transport) { |
||||
this.client = client; |
||||
this.content = content; |
||||
this.transport = transport; |
||||
} |
||||
|
||||
public Transport getTransport() { |
||||
return transport; |
||||
} |
||||
|
||||
public ClientHead getClient() { |
||||
return client; |
||||
} |
||||
|
||||
public ByteBuf getContent() { |
||||
return content; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,26 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.messages; |
||||
|
||||
import java.util.UUID; |
||||
|
||||
public class XHROptionsMessage extends XHRPostMessage { |
||||
|
||||
public XHROptionsMessage(String origin, UUID sessionId) { |
||||
super(origin, sessionId); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,26 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.messages; |
||||
|
||||
import java.util.UUID; |
||||
|
||||
public class XHRPostMessage extends HttpMessage { |
||||
|
||||
public XHRPostMessage(String origin, UUID sessionId) { |
||||
super(origin, sessionId); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,86 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.misc; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.Iterator; |
||||
import java.util.List; |
||||
|
||||
public class CompositeIterable<T> implements Iterable<T>, Iterator<T> { |
||||
|
||||
private List<Iterable<T>> iterablesList; |
||||
private Iterable<T>[] iterables; |
||||
|
||||
private Iterator<Iterator<T>> listIterator; |
||||
private Iterator<T> currentIterator; |
||||
|
||||
public CompositeIterable(List<Iterable<T>> iterables) { |
||||
this.iterablesList = iterables; |
||||
} |
||||
|
||||
public CompositeIterable(Iterable<T> ... iterables) { |
||||
this.iterables = iterables; |
||||
} |
||||
|
||||
public CompositeIterable(CompositeIterable<T> iterable) { |
||||
this.iterables = iterable.iterables; |
||||
this.iterablesList = iterable.iterablesList; |
||||
} |
||||
|
||||
@Override |
||||
public Iterator<T> iterator() { |
||||
List<Iterator<T>> iterators = new ArrayList<Iterator<T>>(); |
||||
if (iterables != null) { |
||||
for (Iterable<T> iterable : iterables) { |
||||
iterators.add(iterable.iterator()); |
||||
} |
||||
} else { |
||||
for (Iterable<T> iterable : iterablesList) { |
||||
iterators.add(iterable.iterator()); |
||||
} |
||||
} |
||||
listIterator = iterators.iterator(); |
||||
currentIterator = null; |
||||
return this; |
||||
} |
||||
|
||||
@Override |
||||
public boolean hasNext() { |
||||
if (currentIterator == null || !currentIterator.hasNext()) { |
||||
while (listIterator.hasNext()) { |
||||
Iterator<T> iterator = listIterator.next(); |
||||
if (iterator.hasNext()) { |
||||
currentIterator = iterator; |
||||
return true; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
return currentIterator.hasNext(); |
||||
} |
||||
|
||||
@Override |
||||
public T next() { |
||||
hasNext(); |
||||
return currentIterator.next(); |
||||
} |
||||
|
||||
@Override |
||||
public void remove() { |
||||
currentIterator.remove(); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,49 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.misc; |
||||
|
||||
import java.util.AbstractCollection; |
||||
import java.util.Iterator; |
||||
|
||||
public class IterableCollection<T> extends AbstractCollection<T> { |
||||
|
||||
private final CompositeIterable<T> iterable; |
||||
|
||||
public IterableCollection(Iterable<T> iterable) { |
||||
this(new CompositeIterable(iterable)); |
||||
} |
||||
|
||||
public IterableCollection(CompositeIterable<T> iterable) { |
||||
this.iterable = iterable; |
||||
} |
||||
|
||||
@Override |
||||
public Iterator<T> iterator() { |
||||
return new CompositeIterable<T>(iterable).iterator(); |
||||
} |
||||
|
||||
@Override |
||||
public int size() { |
||||
Iterator<T> iterator = new CompositeIterable<T>(iterable).iterator(); |
||||
int count = 0; |
||||
while (iterator.hasNext()) { |
||||
iterator.next(); |
||||
count++; |
||||
} |
||||
return count; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,39 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.namespace; |
||||
|
||||
import java.util.Queue; |
||||
import java.util.concurrent.ConcurrentLinkedQueue; |
||||
|
||||
import com.fr.third.socketio.listener.DataListener; |
||||
|
||||
public class EventEntry<T> { |
||||
|
||||
private final Queue<DataListener<T>> listeners = new ConcurrentLinkedQueue<DataListener<T>>();; |
||||
|
||||
public EventEntry() { |
||||
super(); |
||||
} |
||||
|
||||
public void addListener(DataListener<T> listener) { |
||||
listeners.add(listener); |
||||
} |
||||
|
||||
public Queue<DataListener<T>> getListeners() { |
||||
return listeners; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,381 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.namespace; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.Collection; |
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.Queue; |
||||
import java.util.Set; |
||||
import java.util.UUID; |
||||
import java.util.concurrent.ConcurrentLinkedQueue; |
||||
import java.util.concurrent.ConcurrentMap; |
||||
|
||||
import com.fr.third.socketio.AckMode; |
||||
import com.fr.third.socketio.AckRequest; |
||||
import com.fr.third.socketio.BroadcastOperations; |
||||
import com.fr.third.socketio.Configuration; |
||||
import com.fr.third.socketio.MultiTypeArgs; |
||||
import com.fr.third.socketio.SocketIOClient; |
||||
import com.fr.third.socketio.SocketIONamespace; |
||||
import com.fr.third.socketio.annotation.ScannerEngine; |
||||
import com.fr.third.socketio.listener.*; |
||||
import com.fr.third.socketio.listener.ConnectListener; |
||||
import com.fr.third.socketio.listener.DataListener; |
||||
import com.fr.third.socketio.listener.DisconnectListener; |
||||
import com.fr.third.socketio.listener.ExceptionListener; |
||||
import com.fr.third.socketio.listener.MultiTypeEventListener; |
||||
import com.fr.third.socketio.listener.PingListener; |
||||
import com.fr.third.socketio.protocol.JsonSupport; |
||||
import com.fr.third.socketio.protocol.Packet; |
||||
import com.fr.third.socketio.store.StoreFactory; |
||||
import com.fr.third.socketio.store.pubsub.JoinLeaveMessage; |
||||
import com.fr.third.socketio.store.pubsub.PubSubType; |
||||
import com.fr.third.socketio.transport.NamespaceClient; |
||||
|
||||
import io.netty.util.internal.PlatformDependent; |
||||
|
||||
/** |
||||
* Hub object for all clients in one namespace. |
||||
* Namespace shares by different namespace-clients. |
||||
* |
||||
* @see NamespaceClient |
||||
*/ |
||||
public class Namespace implements SocketIONamespace { |
||||
|
||||
public static final String DEFAULT_NAME = ""; |
||||
|
||||
private final ScannerEngine engine = new ScannerEngine(); |
||||
private final ConcurrentMap<String, EventEntry<?>> eventListeners = PlatformDependent.newConcurrentHashMap(); |
||||
private final Queue<ConnectListener> connectListeners = new ConcurrentLinkedQueue<ConnectListener>(); |
||||
private final Queue<DisconnectListener> disconnectListeners = new ConcurrentLinkedQueue<DisconnectListener>(); |
||||
private final Queue<PingListener> pingListeners = new ConcurrentLinkedQueue<PingListener>(); |
||||
|
||||
private final Map<UUID, SocketIOClient> allClients = PlatformDependent.newConcurrentHashMap(); |
||||
private final ConcurrentMap<String, Set<UUID>> roomClients = PlatformDependent.newConcurrentHashMap(); |
||||
private final ConcurrentMap<UUID, Set<String>> clientRooms = PlatformDependent.newConcurrentHashMap(); |
||||
|
||||
private final String name; |
||||
private final AckMode ackMode; |
||||
private final JsonSupport jsonSupport; |
||||
private final StoreFactory storeFactory; |
||||
private final ExceptionListener exceptionListener; |
||||
|
||||
public Namespace(String name, Configuration configuration) { |
||||
super(); |
||||
this.name = name; |
||||
this.jsonSupport = configuration.getJsonSupport(); |
||||
this.storeFactory = configuration.getStoreFactory(); |
||||
this.exceptionListener = configuration.getExceptionListener(); |
||||
this.ackMode = configuration.getAckMode(); |
||||
} |
||||
|
||||
public void addClient(SocketIOClient client) { |
||||
allClients.put(client.getSessionId(), client); |
||||
} |
||||
|
||||
@Override |
||||
public String getName() { |
||||
return name; |
||||
} |
||||
|
||||
@Override |
||||
public void addMultiTypeEventListener(String eventName, MultiTypeEventListener listener, |
||||
Class<?>... eventClass) { |
||||
EventEntry entry = eventListeners.get(eventName); |
||||
if (entry == null) { |
||||
entry = new EventEntry(); |
||||
EventEntry<?> oldEntry = eventListeners.putIfAbsent(eventName, entry); |
||||
if (oldEntry != null) { |
||||
entry = oldEntry; |
||||
} |
||||
} |
||||
entry.addListener(listener); |
||||
jsonSupport.addEventMapping(name, eventName, eventClass); |
||||
} |
||||
|
||||
@Override |
||||
public void removeAllListeners(String eventName) { |
||||
EventEntry<?> entry = eventListeners.remove(eventName); |
||||
if (entry != null) { |
||||
jsonSupport.removeEventMapping(name, eventName); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
@SuppressWarnings({"unchecked", "rawtypes"}) |
||||
public <T> void addEventListener(String eventName, Class<T> eventClass, DataListener<T> listener) { |
||||
EventEntry entry = eventListeners.get(eventName); |
||||
if (entry == null) { |
||||
entry = new EventEntry<T>(); |
||||
EventEntry<?> oldEntry = eventListeners.putIfAbsent(eventName, entry); |
||||
if (oldEntry != null) { |
||||
entry = oldEntry; |
||||
} |
||||
} |
||||
entry.addListener(listener); |
||||
jsonSupport.addEventMapping(name, eventName, eventClass); |
||||
} |
||||
|
||||
@SuppressWarnings({"rawtypes", "unchecked"}) |
||||
public void onEvent(NamespaceClient client, String eventName, List<Object> args, AckRequest ackRequest) { |
||||
EventEntry entry = eventListeners.get(eventName); |
||||
if (entry == null) { |
||||
return; |
||||
} |
||||
|
||||
try { |
||||
Queue<DataListener> listeners = entry.getListeners(); |
||||
for (DataListener dataListener : listeners) { |
||||
Object data = getEventData(args, dataListener); |
||||
dataListener.onData(client, data, ackRequest); |
||||
} |
||||
} catch (Exception e) { |
||||
exceptionListener.onEventException(e, args, client); |
||||
if (ackMode == AckMode.AUTO_SUCCESS_ONLY) { |
||||
return; |
||||
} |
||||
} |
||||
|
||||
sendAck(ackRequest); |
||||
} |
||||
|
||||
private void sendAck(AckRequest ackRequest) { |
||||
if (ackMode == AckMode.AUTO || ackMode == AckMode.AUTO_SUCCESS_ONLY) { |
||||
// send ack response if it not executed
|
||||
// during {@link DataListener#onData} invocation
|
||||
ackRequest.sendAckData(Collections.emptyList()); |
||||
} |
||||
} |
||||
|
||||
private Object getEventData(List<Object> args, DataListener<?> dataListener) { |
||||
if (dataListener instanceof MultiTypeEventListener) { |
||||
return new MultiTypeArgs(args); |
||||
} else { |
||||
if (!args.isEmpty()) { |
||||
return args.get(0); |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
public void addDisconnectListener(DisconnectListener listener) { |
||||
disconnectListeners.add(listener); |
||||
} |
||||
|
||||
public void onDisconnect(SocketIOClient client) { |
||||
Set<String> joinedRooms = client.getAllRooms(); |
||||
allClients.remove(client.getSessionId()); |
||||
|
||||
leave(getName(), client.getSessionId()); |
||||
storeFactory.pubSubStore().publish(PubSubType.LEAVE, new JoinLeaveMessage(client.getSessionId(), getName(), getName())); |
||||
|
||||
for (String joinedRoom : joinedRooms) { |
||||
leave(roomClients, joinedRoom, client.getSessionId()); |
||||
} |
||||
clientRooms.remove(client.getSessionId()); |
||||
|
||||
try { |
||||
for (DisconnectListener listener : disconnectListeners) { |
||||
listener.onDisconnect(client); |
||||
} |
||||
} catch (Exception e) { |
||||
exceptionListener.onDisconnectException(e, client); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void addConnectListener(ConnectListener listener) { |
||||
connectListeners.add(listener); |
||||
} |
||||
|
||||
public void onConnect(SocketIOClient client) { |
||||
join(getName(), client.getSessionId()); |
||||
storeFactory.pubSubStore().publish(PubSubType.JOIN, new JoinLeaveMessage(client.getSessionId(), getName(), getName())); |
||||
|
||||
try { |
||||
for (ConnectListener listener : connectListeners) { |
||||
listener.onConnect(client); |
||||
} |
||||
} catch (Exception e) { |
||||
exceptionListener.onConnectException(e, client); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void addPingListener(PingListener listener) { |
||||
pingListeners.add(listener); |
||||
} |
||||
|
||||
public void onPing(SocketIOClient client) { |
||||
try { |
||||
for (PingListener listener : pingListeners) { |
||||
listener.onPing(client); |
||||
} |
||||
} catch (Exception e) { |
||||
exceptionListener.onPingException(e, client); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public BroadcastOperations getBroadcastOperations() { |
||||
return new BroadcastOperations(allClients.values(), storeFactory); |
||||
} |
||||
|
||||
@Override |
||||
public BroadcastOperations getRoomOperations(String room) { |
||||
return new BroadcastOperations(getRoomClients(room), storeFactory); |
||||
} |
||||
|
||||
@Override |
||||
public int hashCode() { |
||||
final int prime = 31; |
||||
int result = 1; |
||||
result = prime * result + ((name == null) ? 0 : name.hashCode()); |
||||
return result; |
||||
} |
||||
|
||||
@Override |
||||
public boolean equals(Object obj) { |
||||
if (this == obj) |
||||
return true; |
||||
if (obj == null) |
||||
return false; |
||||
if (getClass() != obj.getClass()) |
||||
return false; |
||||
Namespace other = (Namespace) obj; |
||||
if (name == null) { |
||||
if (other.name != null) |
||||
return false; |
||||
} else if (!name.equals(other.name)) |
||||
return false; |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
public void addListeners(Object listeners) { |
||||
addListeners(listeners, listeners.getClass()); |
||||
} |
||||
|
||||
@Override |
||||
public void addListeners(Object listeners, Class<?> listenersClass) { |
||||
engine.scan(this, listeners, listenersClass); |
||||
} |
||||
|
||||
public void joinRoom(String room, UUID sessionId) { |
||||
join(room, sessionId); |
||||
storeFactory.pubSubStore().publish(PubSubType.JOIN, new JoinLeaveMessage(sessionId, room, getName())); |
||||
} |
||||
|
||||
public void dispatch(String room, Packet packet) { |
||||
Iterable<SocketIOClient> clients = getRoomClients(room); |
||||
|
||||
for (SocketIOClient socketIOClient : clients) { |
||||
socketIOClient.send(packet); |
||||
} |
||||
} |
||||
|
||||
private <K, V> void join(ConcurrentMap<K, Set<V>> map, K key, V value) { |
||||
Set<V> clients = map.get(key); |
||||
if (clients == null) { |
||||
clients = Collections.newSetFromMap(PlatformDependent.<V, Boolean>newConcurrentHashMap()); |
||||
Set<V> oldClients = map.putIfAbsent(key, clients); |
||||
if (oldClients != null) { |
||||
clients = oldClients; |
||||
} |
||||
} |
||||
clients.add(value); |
||||
// object may be changed due to other concurrent call
|
||||
if (clients != map.get(key)) { |
||||
// re-join if queue has been replaced
|
||||
join(map, key, value); |
||||
} |
||||
} |
||||
|
||||
public void join(String room, UUID sessionId) { |
||||
join(roomClients, room, sessionId); |
||||
join(clientRooms, sessionId, room); |
||||
} |
||||
|
||||
public void leaveRoom(String room, UUID sessionId) { |
||||
leave(room, sessionId); |
||||
storeFactory.pubSubStore().publish(PubSubType.LEAVE, new JoinLeaveMessage(sessionId, room, getName())); |
||||
} |
||||
|
||||
private <K, V> void leave(ConcurrentMap<K, Set<V>> map, K room, V sessionId) { |
||||
Set<V> clients = map.get(room); |
||||
if (clients == null) { |
||||
return; |
||||
} |
||||
clients.remove(sessionId); |
||||
|
||||
if (clients.isEmpty()) { |
||||
map.remove(room, Collections.emptySet()); |
||||
} |
||||
} |
||||
|
||||
public void leave(String room, UUID sessionId) { |
||||
leave(roomClients, room, sessionId); |
||||
leave(clientRooms, sessionId, room); |
||||
} |
||||
|
||||
public Set<String> getRooms(SocketIOClient client) { |
||||
Set<String> res = clientRooms.get(client.getSessionId()); |
||||
if (res == null) { |
||||
return Collections.emptySet(); |
||||
} |
||||
return Collections.unmodifiableSet(res); |
||||
} |
||||
|
||||
public Set<String> getRooms() { |
||||
return roomClients.keySet(); |
||||
} |
||||
|
||||
public Iterable<SocketIOClient> getRoomClients(String room) { |
||||
Set<UUID> sessionIds = roomClients.get(room); |
||||
|
||||
if (sessionIds == null) { |
||||
return Collections.emptyList(); |
||||
} |
||||
|
||||
List<SocketIOClient> result = new ArrayList<SocketIOClient>(); |
||||
for (UUID sessionId : sessionIds) { |
||||
SocketIOClient client = allClients.get(sessionId); |
||||
if(client != null) { |
||||
result.add(client); |
||||
} |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
@Override |
||||
public Collection<SocketIOClient> getAllClients() { |
||||
return Collections.unmodifiableCollection(allClients.values()); |
||||
} |
||||
|
||||
public JsonSupport getJsonSupport() { |
||||
return jsonSupport; |
||||
} |
||||
|
||||
@Override |
||||
public SocketIOClient getClient(UUID uuid) { |
||||
return allClients.get(uuid); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,75 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.namespace; |
||||
|
||||
import com.fr.third.socketio.Configuration; |
||||
import com.fr.third.socketio.SocketIOClient; |
||||
import com.fr.third.socketio.SocketIONamespace; |
||||
import io.netty.util.internal.PlatformDependent; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.Collection; |
||||
import java.util.List; |
||||
import java.util.concurrent.ConcurrentMap; |
||||
|
||||
import com.fr.third.socketio.misc.CompositeIterable; |
||||
|
||||
public class NamespacesHub { |
||||
|
||||
private final ConcurrentMap<String, SocketIONamespace> namespaces = PlatformDependent.newConcurrentHashMap(); |
||||
private final Configuration configuration; |
||||
|
||||
public NamespacesHub(Configuration configuration) { |
||||
this.configuration = configuration; |
||||
} |
||||
|
||||
public Namespace create(String name) { |
||||
Namespace namespace = (Namespace) namespaces.get(name); |
||||
if (namespace == null) { |
||||
namespace = new Namespace(name, configuration); |
||||
Namespace oldNamespace = (Namespace) namespaces.putIfAbsent(name, namespace); |
||||
if (oldNamespace != null) { |
||||
namespace = oldNamespace; |
||||
} |
||||
} |
||||
return namespace; |
||||
} |
||||
|
||||
public Iterable<SocketIOClient> getRoomClients(String room) { |
||||
List<Iterable<SocketIOClient>> allClients = new ArrayList<Iterable<SocketIOClient>>(); |
||||
for (SocketIONamespace namespace : namespaces.values()) { |
||||
Iterable<SocketIOClient> clients = ((Namespace)namespace).getRoomClients(room); |
||||
allClients.add(clients); |
||||
} |
||||
return new CompositeIterable<SocketIOClient>(allClients); |
||||
} |
||||
|
||||
public Namespace get(String name) { |
||||
return (Namespace) namespaces.get(name); |
||||
} |
||||
|
||||
public void remove(String name) { |
||||
SocketIONamespace namespace = namespaces.remove(name); |
||||
if (namespace != null) { |
||||
namespace.getBroadcastOperations().disconnect(); |
||||
} |
||||
} |
||||
|
||||
public Collection<SocketIONamespace> getAllNamespaces() { |
||||
return namespaces.values(); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,33 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.protocol; |
||||
|
||||
import java.util.List; |
||||
|
||||
public class AckArgs { |
||||
|
||||
private List<Object> args; |
||||
|
||||
public AckArgs(List<Object> args) { |
||||
super(); |
||||
this.args = args; |
||||
} |
||||
|
||||
public List<Object> getArgs() { |
||||
return args; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,52 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.protocol; |
||||
|
||||
import java.util.UUID; |
||||
|
||||
|
||||
public class AuthPacket { |
||||
|
||||
private final UUID sid; |
||||
private final String[] upgrades; |
||||
private final int pingInterval; |
||||
private final int pingTimeout; |
||||
|
||||
public AuthPacket(UUID sid, String[] upgrades, int pingInterval, int pingTimeout) { |
||||
super(); |
||||
this.sid = sid; |
||||
this.upgrades = upgrades; |
||||
this.pingInterval = pingInterval; |
||||
this.pingTimeout = pingTimeout; |
||||
} |
||||
|
||||
public int getPingInterval() { |
||||
return pingInterval; |
||||
} |
||||
|
||||
public int getPingTimeout() { |
||||
return pingTimeout; |
||||
} |
||||
|
||||
public UUID getSid() { |
||||
return sid; |
||||
} |
||||
|
||||
public String[] getUpgrades() { |
||||
return upgrades; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,42 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.protocol; |
||||
|
||||
import java.util.List; |
||||
|
||||
class Event { |
||||
|
||||
private String name; |
||||
private List<Object> args; |
||||
|
||||
public Event() { |
||||
} |
||||
|
||||
public Event(String name, List<Object> args) { |
||||
super(); |
||||
this.name = name; |
||||
this.args = args; |
||||
} |
||||
|
||||
public List<Object> getArgs() { |
||||
return args; |
||||
} |
||||
|
||||
public String getName() { |
||||
return name; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,360 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.protocol; |
||||
|
||||
import com.fr.third.socketio.AckCallback; |
||||
import com.fr.third.socketio.MultiTypeAckCallback; |
||||
import com.fr.third.socketio.namespace.Namespace; |
||||
import io.netty.buffer.ByteBufInputStream; |
||||
import io.netty.buffer.ByteBufOutputStream; |
||||
import io.netty.util.internal.PlatformDependent; |
||||
|
||||
import java.io.IOException; |
||||
import java.lang.reflect.Type; |
||||
import java.util.ArrayList; |
||||
import java.util.Arrays; |
||||
import java.util.Collections; |
||||
import java.util.HashMap; |
||||
import java.util.Iterator; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
|
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
import com.fr.third.fasterxml.jackson.annotation.JsonInclude.Include; |
||||
import com.fr.third.fasterxml.jackson.core.JsonGenerationException; |
||||
import com.fr.third.fasterxml.jackson.core.JsonGenerator; |
||||
import com.fr.third.fasterxml.jackson.core.JsonParser; |
||||
import com.fr.third.fasterxml.jackson.core.JsonProcessingException; |
||||
import com.fr.third.fasterxml.jackson.core.JsonToken; |
||||
import com.fr.third.fasterxml.jackson.databind.BeanDescription; |
||||
import com.fr.third.fasterxml.jackson.databind.DeserializationContext; |
||||
import com.fr.third.fasterxml.jackson.databind.DeserializationFeature; |
||||
import com.fr.third.fasterxml.jackson.databind.JavaType; |
||||
import com.fr.third.fasterxml.jackson.databind.JsonMappingException; |
||||
import com.fr.third.fasterxml.jackson.databind.JsonNode; |
||||
import com.fr.third.fasterxml.jackson.databind.JsonSerializer; |
||||
import com.fr.third.fasterxml.jackson.databind.Module; |
||||
import com.fr.third.fasterxml.jackson.databind.ObjectMapper; |
||||
import com.fr.third.fasterxml.jackson.databind.SerializationConfig; |
||||
import com.fr.third.fasterxml.jackson.databind.SerializationFeature; |
||||
import com.fr.third.fasterxml.jackson.databind.SerializerProvider; |
||||
import com.fr.third.fasterxml.jackson.databind.deser.std.StdDeserializer; |
||||
import com.fr.third.fasterxml.jackson.databind.jsonFormatVisitors.JsonArrayFormatVisitor; |
||||
import com.fr.third.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatTypes; |
||||
import com.fr.third.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper; |
||||
import com.fr.third.fasterxml.jackson.databind.jsontype.TypeSerializer; |
||||
import com.fr.third.fasterxml.jackson.databind.module.SimpleModule; |
||||
import com.fr.third.fasterxml.jackson.databind.node.ObjectNode; |
||||
import com.fr.third.fasterxml.jackson.databind.ser.BeanSerializerModifier; |
||||
import com.fr.third.fasterxml.jackson.databind.ser.std.StdSerializer; |
||||
import com.fr.third.fasterxml.jackson.databind.type.ArrayType; |
||||
|
||||
public class JacksonJsonSupport implements JsonSupport { |
||||
|
||||
private class AckArgsDeserializer extends StdDeserializer<AckArgs> { |
||||
|
||||
private static final long serialVersionUID = 7810461017389946707L; |
||||
|
||||
protected AckArgsDeserializer() { |
||||
super(AckArgs.class); |
||||
} |
||||
|
||||
@Override |
||||
public AckArgs deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, |
||||
JsonProcessingException { |
||||
List<Object> args = new ArrayList<Object>(); |
||||
AckArgs result = new AckArgs(args); |
||||
|
||||
ObjectMapper mapper = (ObjectMapper) jp.getCodec(); |
||||
JsonNode root = mapper.readTree(jp); |
||||
AckCallback<?> callback = currentAckClass.get(); |
||||
Iterator<JsonNode> iter = root.iterator(); |
||||
int i = 0; |
||||
while (iter.hasNext()) { |
||||
Object val; |
||||
|
||||
Class<?> clazz = callback.getResultClass(); |
||||
if (callback instanceof MultiTypeAckCallback) { |
||||
MultiTypeAckCallback multiTypeAckCallback = (MultiTypeAckCallback) callback; |
||||
clazz = multiTypeAckCallback.getResultClasses()[i]; |
||||
} |
||||
|
||||
JsonNode arg = iter.next(); |
||||
if (arg.isTextual() || arg.isBoolean()) { |
||||
clazz = Object.class; |
||||
} |
||||
|
||||
val = mapper.treeToValue(arg, clazz); |
||||
args.add(val); |
||||
i++; |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
} |
||||
|
||||
public static class EventKey { |
||||
|
||||
private String namespaceName; |
||||
private String eventName; |
||||
|
||||
public EventKey(String namespaceName, String eventName) { |
||||
super(); |
||||
this.namespaceName = namespaceName; |
||||
this.eventName = eventName; |
||||
} |
||||
|
||||
@Override |
||||
public int hashCode() { |
||||
final int prime = 31; |
||||
int result = 1; |
||||
result = prime * result + ((eventName == null) ? 0 : eventName.hashCode()); |
||||
result = prime * result + ((namespaceName == null) ? 0 : namespaceName.hashCode()); |
||||
return result; |
||||
} |
||||
|
||||
@Override |
||||
public boolean equals(Object obj) { |
||||
if (this == obj) |
||||
return true; |
||||
if (obj == null) |
||||
return false; |
||||
if (getClass() != obj.getClass()) |
||||
return false; |
||||
EventKey other = (EventKey) obj; |
||||
if (eventName == null) { |
||||
if (other.eventName != null) |
||||
return false; |
||||
} else if (!eventName.equals(other.eventName)) |
||||
return false; |
||||
if (namespaceName == null) { |
||||
if (other.namespaceName != null) |
||||
return false; |
||||
} else if (!namespaceName.equals(other.namespaceName)) |
||||
return false; |
||||
return true; |
||||
} |
||||
|
||||
} |
||||
|
||||
private class EventDeserializer extends StdDeserializer<Event> { |
||||
|
||||
private static final long serialVersionUID = 8178797221017768689L; |
||||
|
||||
final Map<EventKey, List<Class<?>>> eventMapping = PlatformDependent.newConcurrentHashMap(); |
||||
|
||||
|
||||
protected EventDeserializer() { |
||||
super(Event.class); |
||||
} |
||||
|
||||
@Override |
||||
public Event deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, |
||||
JsonProcessingException { |
||||
ObjectMapper mapper = (ObjectMapper) jp.getCodec(); |
||||
String eventName = jp.nextTextValue(); |
||||
|
||||
EventKey ek = new EventKey(namespaceClass.get(), eventName); |
||||
if (!eventMapping.containsKey(ek)) { |
||||
ek = new EventKey(Namespace.DEFAULT_NAME, eventName); |
||||
if (!eventMapping.containsKey(ek)) { |
||||
return new Event(eventName, Collections.emptyList()); |
||||
} |
||||
} |
||||
|
||||
List<Object> eventArgs = new ArrayList<Object>(); |
||||
Event event = new Event(eventName, eventArgs); |
||||
List<Class<?>> eventClasses = eventMapping.get(ek); |
||||
int i = 0; |
||||
while (true) { |
||||
JsonToken token = jp.nextToken(); |
||||
if (token == JsonToken.END_ARRAY) { |
||||
break; |
||||
} |
||||
if (i > eventClasses.size() - 1) { |
||||
log.debug("Event {} has more args than declared in handler: {}", eventName, null); |
||||
break; |
||||
} |
||||
Class<?> eventClass = eventClasses.get(i); |
||||
Object arg = mapper.readValue(jp, eventClass); |
||||
eventArgs.add(arg); |
||||
i++; |
||||
} |
||||
return event; |
||||
} |
||||
|
||||
} |
||||
|
||||
public static class ByteArraySerializer extends StdSerializer<byte[]> |
||||
{ |
||||
|
||||
private static final long serialVersionUID = 3420082888596468148L; |
||||
|
||||
private final ThreadLocal<List<byte[]>> arrays = new ThreadLocal<List<byte[]>>() { |
||||
@Override |
||||
protected List<byte[]> initialValue() { |
||||
return new ArrayList<byte[]>(); |
||||
}; |
||||
}; |
||||
|
||||
public ByteArraySerializer() { |
||||
super(byte[].class); |
||||
} |
||||
|
||||
@Override |
||||
public boolean isEmpty(byte[] value) { |
||||
return (value == null) || (value.length == 0); |
||||
} |
||||
|
||||
@Override |
||||
public void serialize(byte[] value, JsonGenerator jgen, SerializerProvider provider) |
||||
throws IOException, JsonGenerationException |
||||
{ |
||||
Map<String, Object> map = new HashMap<String, Object>(); |
||||
map.put("num", arrays.get().size()); |
||||
map.put("_placeholder", true); |
||||
jgen.writeObject(map); |
||||
arrays.get().add(value); |
||||
} |
||||
|
||||
@Override |
||||
public void serializeWithType(byte[] value, JsonGenerator jgen, SerializerProvider provider, |
||||
TypeSerializer typeSer) |
||||
throws IOException, JsonGenerationException |
||||
{ |
||||
serialize(value, jgen, provider); |
||||
} |
||||
|
||||
@Override |
||||
public JsonNode getSchema(SerializerProvider provider, Type typeHint) |
||||
{ |
||||
ObjectNode o = createSchemaNode("array", true); |
||||
ObjectNode itemSchema = createSchemaNode("string"); //binary values written as strings?
|
||||
return o.set("items", itemSchema); |
||||
} |
||||
|
||||
@Override |
||||
public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint) |
||||
throws JsonMappingException |
||||
{ |
||||
if (visitor != null) { |
||||
JsonArrayFormatVisitor v2 = visitor.expectArrayFormat(typeHint); |
||||
if (v2 != null) { |
||||
v2.itemsFormat(JsonFormatTypes.STRING); |
||||
} |
||||
} |
||||
} |
||||
|
||||
public List<byte[]> getArrays() { |
||||
return arrays.get(); |
||||
} |
||||
|
||||
public void clear() { |
||||
arrays.set(new ArrayList<byte[]>()); |
||||
} |
||||
|
||||
} |
||||
|
||||
|
||||
private class ExBeanSerializerModifier extends BeanSerializerModifier { |
||||
|
||||
private final ByteArraySerializer serializer = new ByteArraySerializer(); |
||||
|
||||
@Override |
||||
public JsonSerializer<?> modifyArraySerializer(SerializationConfig config, ArrayType valueType, |
||||
BeanDescription beanDesc, JsonSerializer<?> serializer) { |
||||
if (valueType.getRawClass().equals(byte[].class)) { |
||||
return this.serializer; |
||||
} |
||||
|
||||
return super.modifyArraySerializer(config, valueType, beanDesc, serializer); |
||||
} |
||||
|
||||
public ByteArraySerializer getSerializer() { |
||||
return serializer; |
||||
} |
||||
|
||||
} |
||||
|
||||
protected final ExBeanSerializerModifier modifier = new ExBeanSerializerModifier(); |
||||
protected final ThreadLocal<String> namespaceClass = new ThreadLocal<String>(); |
||||
protected final ThreadLocal<AckCallback<?>> currentAckClass = new ThreadLocal<AckCallback<?>>(); |
||||
protected final ObjectMapper objectMapper = new ObjectMapper(); |
||||
protected final EventDeserializer eventDeserializer = new EventDeserializer(); |
||||
protected final AckArgsDeserializer ackArgsDeserializer = new AckArgsDeserializer(); |
||||
|
||||
protected static final Logger log = LoggerFactory.getLogger(JacksonJsonSupport.class); |
||||
|
||||
public JacksonJsonSupport() { |
||||
this(new Module[] {}); |
||||
} |
||||
|
||||
public JacksonJsonSupport(Module... modules) { |
||||
if (modules != null && modules.length > 0) { |
||||
objectMapper.registerModules(modules); |
||||
} |
||||
init(objectMapper); |
||||
} |
||||
|
||||
protected void init(ObjectMapper objectMapper) { |
||||
SimpleModule module = new SimpleModule(); |
||||
module.setSerializerModifier(modifier); |
||||
module.addDeserializer(Event.class, eventDeserializer); |
||||
module.addDeserializer(AckArgs.class, ackArgsDeserializer); |
||||
objectMapper.registerModule(module); |
||||
|
||||
objectMapper.setSerializationInclusion(Include.NON_NULL); |
||||
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); |
||||
objectMapper.configure(SerializationFeature.WRITE_BIGDECIMAL_AS_PLAIN, true); |
||||
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); |
||||
} |
||||
|
||||
@Override |
||||
public void addEventMapping(String namespaceName, String eventName, Class<?> ... eventClass) { |
||||
eventDeserializer.eventMapping.put(new EventKey(namespaceName, eventName), Arrays.asList(eventClass)); |
||||
} |
||||
|
||||
@Override |
||||
public void removeEventMapping(String namespaceName, String eventName) { |
||||
eventDeserializer.eventMapping.remove(new EventKey(namespaceName, eventName)); |
||||
} |
||||
|
||||
@Override |
||||
public <T> T readValue(String namespaceName, ByteBufInputStream src, Class<T> valueType) throws IOException { |
||||
namespaceClass.set(namespaceName); |
||||
return objectMapper.readValue(src, valueType); |
||||
} |
||||
|
||||
@Override |
||||
public AckArgs readAckArgs(ByteBufInputStream src, AckCallback<?> callback) throws IOException { |
||||
currentAckClass.set(callback); |
||||
return objectMapper.readValue(src, AckArgs.class); |
||||
} |
||||
|
||||
@Override |
||||
public void writeValue(ByteBufOutputStream out, Object value) throws IOException { |
||||
modifier.getSerializer().clear(); |
||||
objectMapper.writeValue(out, value); |
||||
} |
||||
|
||||
@Override |
||||
public List<byte[]> getArrays() { |
||||
return modifier.getSerializer().getArrays(); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,45 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.protocol; |
||||
|
||||
import com.fr.third.socketio.AckCallback; |
||||
import io.netty.buffer.ByteBufInputStream; |
||||
import io.netty.buffer.ByteBufOutputStream; |
||||
|
||||
import java.io.IOException; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* JSON infrastructure interface. |
||||
* Allows to implement custom realizations |
||||
* to JSON support operations. |
||||
* |
||||
*/ |
||||
public interface JsonSupport { |
||||
|
||||
AckArgs readAckArgs(ByteBufInputStream src, AckCallback<?> callback) throws IOException; |
||||
|
||||
<T> T readValue(String namespaceName, ByteBufInputStream src, Class<T> valueType) throws IOException; |
||||
|
||||
void writeValue(ByteBufOutputStream out, Object value) throws IOException; |
||||
|
||||
void addEventMapping(String namespaceName, String eventName, Class<?> ... eventClass); |
||||
|
||||
void removeEventMapping(String namespaceName, String eventName); |
||||
|
||||
List<byte[]> getArrays(); |
||||
|
||||
} |
@ -0,0 +1,138 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.protocol; |
||||
|
||||
import com.fr.third.socketio.namespace.Namespace; |
||||
import io.netty.buffer.ByteBuf; |
||||
|
||||
import java.io.Serializable; |
||||
import java.util.ArrayList; |
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
|
||||
public class Packet implements Serializable { |
||||
|
||||
private static final long serialVersionUID = 4560159536486711426L; |
||||
|
||||
private PacketType type; |
||||
private PacketType subType; |
||||
private Long ackId; |
||||
private String name; |
||||
private String nsp = Namespace.DEFAULT_NAME; |
||||
private Object data; |
||||
|
||||
private ByteBuf dataSource; |
||||
private int attachmentsCount; |
||||
private List<ByteBuf> attachments = Collections.emptyList(); |
||||
|
||||
protected Packet() { |
||||
} |
||||
|
||||
public Packet(PacketType type) { |
||||
super(); |
||||
this.type = type; |
||||
} |
||||
|
||||
public PacketType getSubType() { |
||||
return subType; |
||||
} |
||||
|
||||
public void setSubType(PacketType subType) { |
||||
this.subType = subType; |
||||
} |
||||
|
||||
public PacketType getType() { |
||||
return type; |
||||
} |
||||
|
||||
public void setData(Object data) { |
||||
this.data = data; |
||||
} |
||||
|
||||
/** |
||||
* Get packet data |
||||
* |
||||
* @param <T> the type data |
||||
* |
||||
* <pre> |
||||
* @return <b>json object</b> for PacketType.JSON type |
||||
* <b>message</b> for PacketType.MESSAGE type |
||||
* </pre> |
||||
*/ |
||||
public <T> T getData() { |
||||
return (T)data; |
||||
} |
||||
|
||||
public void setNsp(String endpoint) { |
||||
this.nsp = endpoint; |
||||
} |
||||
|
||||
public String getNsp() { |
||||
return nsp; |
||||
} |
||||
|
||||
public String getName() { |
||||
return name; |
||||
} |
||||
|
||||
public void setName(String name) { |
||||
this.name = name; |
||||
} |
||||
|
||||
public Long getAckId() { |
||||
return ackId; |
||||
} |
||||
|
||||
public void setAckId(Long ackId) { |
||||
this.ackId = ackId; |
||||
} |
||||
|
||||
public boolean isAckRequested() { |
||||
return getAckId() != null; |
||||
} |
||||
|
||||
public void initAttachments(int attachmentsCount) { |
||||
this.attachmentsCount = attachmentsCount; |
||||
this.attachments = new ArrayList<ByteBuf>(attachmentsCount); |
||||
} |
||||
public void addAttachment(ByteBuf attachment) { |
||||
if (this.attachments.size() < attachmentsCount) { |
||||
this.attachments.add(attachment); |
||||
} |
||||
} |
||||
public List<ByteBuf> getAttachments() { |
||||
return attachments; |
||||
} |
||||
public boolean hasAttachments() { |
||||
return attachmentsCount != 0; |
||||
} |
||||
public boolean isAttachmentsLoaded() { |
||||
return this.attachments.size() == attachmentsCount; |
||||
} |
||||
|
||||
public ByteBuf getDataSource() { |
||||
return dataSource; |
||||
} |
||||
public void setDataSource(ByteBuf dataSource) { |
||||
this.dataSource = dataSource; |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return "Packet [type=" + type + ", ackId=" + ackId + "]"; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,313 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.protocol; |
||||
|
||||
import com.fr.third.socketio.AckCallback; |
||||
import com.fr.third.socketio.ack.AckManager; |
||||
import com.fr.third.socketio.handler.ClientHead; |
||||
import io.netty.buffer.ByteBuf; |
||||
import io.netty.buffer.ByteBufInputStream; |
||||
import io.netty.buffer.Unpooled; |
||||
import io.netty.handler.codec.base64.Base64; |
||||
import io.netty.util.CharsetUtil; |
||||
|
||||
import java.io.IOException; |
||||
import java.net.URLDecoder; |
||||
import java.util.LinkedList; |
||||
import java.util.UUID; |
||||
|
||||
public class PacketDecoder { |
||||
|
||||
private final UTF8CharsScanner utf8scanner = new UTF8CharsScanner(); |
||||
|
||||
private final ByteBuf QUOTES = Unpooled.copiedBuffer("\"", CharsetUtil.UTF_8); |
||||
|
||||
private final JsonSupport jsonSupport; |
||||
private final AckManager ackManager; |
||||
|
||||
public PacketDecoder(JsonSupport jsonSupport, AckManager ackManager) { |
||||
this.jsonSupport = jsonSupport; |
||||
this.ackManager = ackManager; |
||||
} |
||||
|
||||
private boolean isStringPacket(ByteBuf content) { |
||||
return content.getByte(content.readerIndex()) == 0x0; |
||||
} |
||||
|
||||
// TODO optimize
|
||||
public ByteBuf preprocessJson(Integer jsonIndex, ByteBuf content) throws IOException { |
||||
String packet = URLDecoder.decode(content.toString(CharsetUtil.UTF_8), CharsetUtil.UTF_8.name()); |
||||
|
||||
if (jsonIndex != null) { |
||||
/** |
||||
* double escaping is required for escaped new lines because unescaping of new lines can be done safely on server-side |
||||
* (c) socket.io.js |
||||
* |
||||
* @see https://github.com/Automattic/socket.io-client/blob/1.3.3/socket.io.js#L2682
|
||||
*/ |
||||
packet = packet.replace("\\\\n", "\\n"); |
||||
|
||||
// skip "d="
|
||||
packet = packet.substring(2); |
||||
} |
||||
|
||||
return Unpooled.wrappedBuffer(packet.getBytes(CharsetUtil.UTF_8)); |
||||
} |
||||
|
||||
// fastest way to parse chars to int
|
||||
private long readLong(ByteBuf chars, int length) { |
||||
long result = 0; |
||||
for (int i = chars.readerIndex(); i < chars.readerIndex() + length; i++) { |
||||
int digit = ((int)chars.getByte(i) & 0xF); |
||||
for (int j = 0; j < chars.readerIndex() + length-1-i; j++) { |
||||
digit *= 10; |
||||
} |
||||
result += digit; |
||||
} |
||||
chars.readerIndex(chars.readerIndex() + length); |
||||
return result; |
||||
} |
||||
|
||||
private PacketType readType(ByteBuf buffer) { |
||||
int typeId = buffer.readByte() & 0xF; |
||||
return PacketType.valueOf(typeId); |
||||
} |
||||
|
||||
private PacketType readInnerType(ByteBuf buffer) { |
||||
int typeId = buffer.readByte() & 0xF; |
||||
return PacketType.valueOfInner(typeId); |
||||
} |
||||
|
||||
@Deprecated |
||||
public Packet decodePacket(String string, UUID uuid) throws IOException { |
||||
ByteBuf buf = Unpooled.copiedBuffer(string, CharsetUtil.UTF_8); |
||||
try { |
||||
return null; |
||||
} finally { |
||||
buf.release(); |
||||
} |
||||
} |
||||
|
||||
private boolean hasLengthHeader(ByteBuf buffer) { |
||||
for (int i = 0; i < Math.min(buffer.readableBytes(), 10); i++) { |
||||
byte b = buffer.getByte(buffer.readerIndex() + i); |
||||
if (b == (byte)':' && i > 0) { |
||||
return true; |
||||
} |
||||
if (b > 57 || b < 48) { |
||||
return false; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
public Packet decodePackets(ByteBuf buffer, ClientHead client) throws IOException { |
||||
if (isStringPacket(buffer)) { |
||||
// TODO refactor
|
||||
int maxLength = Math.min(buffer.readableBytes(), 10); |
||||
int headEndIndex = buffer.bytesBefore(maxLength, (byte)-1); |
||||
if (headEndIndex == -1) { |
||||
headEndIndex = buffer.bytesBefore(maxLength, (byte)0x3f); |
||||
} |
||||
int len = (int) readLong(buffer, headEndIndex); |
||||
|
||||
ByteBuf frame = buffer.slice(buffer.readerIndex() + 1, len); |
||||
// skip this frame
|
||||
buffer.readerIndex(buffer.readerIndex() + 1 + len); |
||||
return decode(client, frame); |
||||
} else if (hasLengthHeader(buffer)) { |
||||
// TODO refactor
|
||||
int lengthEndIndex = buffer.bytesBefore((byte)':'); |
||||
int lenHeader = (int) readLong(buffer, lengthEndIndex); |
||||
int len = utf8scanner.getActualLength(buffer, lenHeader); |
||||
|
||||
ByteBuf frame = buffer.slice(buffer.readerIndex() + 1, len); |
||||
// skip this frame
|
||||
buffer.readerIndex(buffer.readerIndex() + 1 + len); |
||||
return decode(client, frame); |
||||
} |
||||
return decode(client, buffer); |
||||
} |
||||
|
||||
private String readString(ByteBuf frame) { |
||||
return readString(frame, frame.readableBytes()); |
||||
} |
||||
|
||||
private String readString(ByteBuf frame, int size) { |
||||
byte[] bytes = new byte[size]; |
||||
frame.readBytes(bytes); |
||||
return new String(bytes, CharsetUtil.UTF_8); |
||||
} |
||||
|
||||
private Packet decode(ClientHead head, ByteBuf frame) throws IOException { |
||||
if ((frame.getByte(0) == 'b' && frame.getByte(1) == '4') |
||||
|| frame.getByte(0) == 4 || frame.getByte(0) == 1) { |
||||
return parseBinary(head, frame); |
||||
} |
||||
PacketType type = readType(frame); |
||||
Packet packet = new Packet(type); |
||||
|
||||
if (type == PacketType.PING) { |
||||
packet.setData(readString(frame)); |
||||
return packet; |
||||
} |
||||
|
||||
if (!frame.isReadable()) { |
||||
return packet; |
||||
} |
||||
|
||||
PacketType innerType = readInnerType(frame); |
||||
packet.setSubType(innerType); |
||||
|
||||
parseHeader(frame, packet, innerType); |
||||
parseBody(head, frame, packet); |
||||
return packet; |
||||
} |
||||
|
||||
private void parseHeader(ByteBuf frame, Packet packet, PacketType innerType) { |
||||
int endIndex = frame.bytesBefore((byte)'['); |
||||
if (endIndex <= 0) { |
||||
return; |
||||
} |
||||
|
||||
int attachmentsDividerIndex = frame.bytesBefore(endIndex, (byte)'-'); |
||||
boolean hasAttachments = attachmentsDividerIndex != -1; |
||||
if (hasAttachments && (PacketType.BINARY_EVENT.equals(innerType) |
||||
|| PacketType.BINARY_ACK.equals(innerType))) { |
||||
int attachments = (int) readLong(frame, attachmentsDividerIndex); |
||||
packet.initAttachments(attachments); |
||||
frame.readerIndex(frame.readerIndex() + 1); |
||||
|
||||
endIndex -= attachmentsDividerIndex + 1; |
||||
} |
||||
if (endIndex == 0) { |
||||
return; |
||||
} |
||||
|
||||
// TODO optimize
|
||||
boolean hasNsp = frame.bytesBefore(endIndex, (byte)',') != -1; |
||||
if (hasNsp) { |
||||
String nspAckId = readString(frame, endIndex); |
||||
String[] parts = nspAckId.split(","); |
||||
String nsp = parts[0]; |
||||
packet.setNsp(nsp); |
||||
if (parts.length > 1) { |
||||
String ackId = parts[1]; |
||||
packet.setAckId(Long.valueOf(ackId)); |
||||
} |
||||
} else { |
||||
long ackId = readLong(frame, endIndex); |
||||
packet.setAckId(ackId); |
||||
} |
||||
} |
||||
|
||||
private Packet parseBinary(ClientHead head, ByteBuf frame) throws IOException { |
||||
if (frame.getByte(0) == 1) { |
||||
frame.readByte(); |
||||
int headEndIndex = frame.bytesBefore((byte)-1); |
||||
int len = (int) readLong(frame, headEndIndex); |
||||
ByteBuf oldFrame = frame; |
||||
frame = frame.slice(oldFrame.readerIndex() + 1, len); |
||||
oldFrame.readerIndex(oldFrame.readerIndex() + 1 + len); |
||||
} |
||||
|
||||
if (frame.getByte(0) == 'b' && frame.getByte(1) == '4') { |
||||
frame.readShort(); |
||||
} else if (frame.getByte(0) == 4) { |
||||
frame.readByte(); |
||||
} |
||||
|
||||
Packet binaryPacket = head.getLastBinaryPacket(); |
||||
if (binaryPacket != null) { |
||||
if (frame.getByte(0) == 'b' && frame.getByte(1) == '4') { |
||||
binaryPacket.addAttachment(Unpooled.copiedBuffer(frame)); |
||||
} else { |
||||
ByteBuf attachBuf = Base64.encode(frame); |
||||
binaryPacket.addAttachment(Unpooled.copiedBuffer(attachBuf)); |
||||
attachBuf.release(); |
||||
} |
||||
frame.readerIndex(frame.readerIndex() + frame.readableBytes()); |
||||
|
||||
if (binaryPacket.isAttachmentsLoaded()) { |
||||
LinkedList<ByteBuf> slices = new LinkedList<ByteBuf>(); |
||||
ByteBuf source = binaryPacket.getDataSource(); |
||||
for (int i = 0; i < binaryPacket.getAttachments().size(); i++) { |
||||
ByteBuf attachment = binaryPacket.getAttachments().get(i); |
||||
ByteBuf scanValue = Unpooled.copiedBuffer("{\"_placeholder\":true,\"num\":" + i + "}", CharsetUtil.UTF_8); |
||||
int pos = PacketEncoder.find(source, scanValue); |
||||
if (pos == -1) { |
||||
scanValue = Unpooled.copiedBuffer("{\"num\":" + i + ",\"_placeholder\":true}", CharsetUtil.UTF_8); |
||||
pos = PacketEncoder.find(source, scanValue); |
||||
if (pos == -1) { |
||||
throw new IllegalStateException("Can't find attachment by index: " + i + " in packet source"); |
||||
} |
||||
} |
||||
|
||||
ByteBuf prefixBuf = source.slice(source.readerIndex(), pos - source.readerIndex()); |
||||
slices.add(prefixBuf); |
||||
slices.add(QUOTES); |
||||
slices.add(attachment); |
||||
slices.add(QUOTES); |
||||
|
||||
source.readerIndex(pos + scanValue.readableBytes()); |
||||
} |
||||
slices.add(source.slice()); |
||||
|
||||
ByteBuf compositeBuf = Unpooled.wrappedBuffer(slices.toArray(new ByteBuf[slices.size()])); |
||||
parseBody(head, compositeBuf, binaryPacket); |
||||
head.setLastBinaryPacket(null); |
||||
return binaryPacket; |
||||
} |
||||
} |
||||
return new Packet(PacketType.MESSAGE); |
||||
} |
||||
|
||||
private void parseBody(ClientHead head, ByteBuf frame, Packet packet) throws IOException { |
||||
if (packet.getType() == PacketType.MESSAGE) { |
||||
if (packet.getSubType() == PacketType.CONNECT |
||||
|| packet.getSubType() == PacketType.DISCONNECT) { |
||||
packet.setNsp(readString(frame)); |
||||
} |
||||
|
||||
if (packet.hasAttachments() && !packet.isAttachmentsLoaded()) { |
||||
packet.setDataSource(Unpooled.copiedBuffer(frame)); |
||||
frame.readerIndex(frame.readableBytes()); |
||||
head.setLastBinaryPacket(packet); |
||||
} |
||||
|
||||
if (packet.hasAttachments() && !packet.isAttachmentsLoaded()) { |
||||
return; |
||||
} |
||||
|
||||
if (packet.getSubType() == PacketType.ACK |
||||
|| packet.getSubType() == PacketType.BINARY_ACK) { |
||||
ByteBufInputStream in = new ByteBufInputStream(frame); |
||||
AckCallback<?> callback = ackManager.getCallback(head.getSessionId(), packet.getAckId()); |
||||
AckArgs args = jsonSupport.readAckArgs(in, callback); |
||||
packet.setData(args.getArgs()); |
||||
} |
||||
|
||||
if (packet.getSubType() == PacketType.EVENT |
||||
|| packet.getSubType() == PacketType.BINARY_EVENT) { |
||||
ByteBufInputStream in = new ByteBufInputStream(frame); |
||||
Event event = jsonSupport.readValue(packet.getNsp(), in, Event.class); |
||||
packet.setName(event.getName()); |
||||
packet.setData(event.getArgs()); |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,352 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.protocol; |
||||
|
||||
import com.fr.third.socketio.Configuration; |
||||
import io.netty.buffer.ByteBuf; |
||||
import io.netty.buffer.ByteBufAllocator; |
||||
import io.netty.buffer.ByteBufOutputStream; |
||||
import io.netty.buffer.Unpooled; |
||||
import io.netty.handler.codec.base64.Base64; |
||||
import io.netty.handler.codec.base64.Base64Dialect; |
||||
import io.netty.util.CharsetUtil; |
||||
|
||||
import java.io.IOException; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
import java.util.Queue; |
||||
|
||||
public class PacketEncoder { |
||||
|
||||
private static final byte[] BINARY_HEADER = "b4".getBytes(CharsetUtil.UTF_8); |
||||
private static final byte[] B64_DELIMITER = new byte[] {':'}; |
||||
private static final byte[] JSONP_HEAD = "___eio[".getBytes(CharsetUtil.UTF_8); |
||||
private static final byte[] JSONP_START = "]('".getBytes(CharsetUtil.UTF_8); |
||||
private static final byte[] JSONP_END = "');".getBytes(CharsetUtil.UTF_8); |
||||
|
||||
private final JsonSupport jsonSupport; |
||||
private final Configuration configuration; |
||||
|
||||
public PacketEncoder(Configuration configuration, JsonSupport jsonSupport) { |
||||
this.jsonSupport = jsonSupport; |
||||
this.configuration = configuration; |
||||
} |
||||
|
||||
public JsonSupport getJsonSupport() { |
||||
return jsonSupport; |
||||
} |
||||
|
||||
public ByteBuf allocateBuffer(ByteBufAllocator allocator) { |
||||
if (configuration.isPreferDirectBuffer()) { |
||||
return allocator.ioBuffer(); |
||||
} |
||||
|
||||
return allocator.heapBuffer(); |
||||
} |
||||
|
||||
public void encodeJsonP(Integer jsonpIndex, Queue<Packet> packets, ByteBuf out, ByteBufAllocator allocator, int limit) throws IOException { |
||||
boolean jsonpMode = jsonpIndex != null; |
||||
|
||||
ByteBuf buf = allocateBuffer(allocator); |
||||
|
||||
int i = 0; |
||||
while (true) { |
||||
Packet packet = packets.poll(); |
||||
if (packet == null || i == limit) { |
||||
break; |
||||
} |
||||
|
||||
ByteBuf packetBuf = allocateBuffer(allocator); |
||||
encodePacket(packet, packetBuf, allocator, true); |
||||
|
||||
int packetSize = packetBuf.writerIndex(); |
||||
buf.writeBytes(toChars(packetSize)); |
||||
buf.writeBytes(B64_DELIMITER); |
||||
buf.writeBytes(packetBuf); |
||||
|
||||
packetBuf.release(); |
||||
|
||||
i++; |
||||
|
||||
for (ByteBuf attachment : packet.getAttachments()) { |
||||
ByteBuf encodedBuf = Base64.encode(attachment, Base64Dialect.URL_SAFE); |
||||
buf.writeBytes(toChars(encodedBuf.readableBytes() + 2)); |
||||
buf.writeBytes(B64_DELIMITER); |
||||
buf.writeBytes(BINARY_HEADER); |
||||
buf.writeBytes(encodedBuf); |
||||
} |
||||
} |
||||
|
||||
if (jsonpMode) { |
||||
out.writeBytes(JSONP_HEAD); |
||||
out.writeBytes(toChars(jsonpIndex)); |
||||
out.writeBytes(JSONP_START); |
||||
} |
||||
|
||||
processUtf8(buf, out, jsonpMode); |
||||
buf.release(); |
||||
|
||||
if (jsonpMode) { |
||||
out.writeBytes(JSONP_END); |
||||
} |
||||
} |
||||
|
||||
private void processUtf8(ByteBuf in, ByteBuf out, boolean jsonpMode) { |
||||
while (in.isReadable()) { |
||||
short value = (short) (in.readByte() & 0xFF); |
||||
if (value >>> 7 == 0) { |
||||
if (jsonpMode && (value == '\\' || value == '\'')) { |
||||
out.writeByte('\\'); |
||||
} |
||||
out.writeByte(value); |
||||
} else { |
||||
out.writeByte(((value >>> 6) | 0xC0)); |
||||
out.writeByte(((value & 0x3F) | 0x80)); |
||||
} |
||||
} |
||||
} |
||||
|
||||
public void encodePackets(Queue<Packet> packets, ByteBuf buffer, ByteBufAllocator allocator, int limit) throws IOException { |
||||
int i = 0; |
||||
while (true) { |
||||
Packet packet = packets.poll(); |
||||
if (packet == null || i == limit) { |
||||
break; |
||||
} |
||||
encodePacket(packet, buffer, allocator, false); |
||||
|
||||
i++; |
||||
|
||||
for (ByteBuf attachment : packet.getAttachments()) { |
||||
buffer.writeByte(1); |
||||
buffer.writeBytes(longToBytes(attachment.readableBytes() + 1)); |
||||
buffer.writeByte(0xff); |
||||
buffer.writeByte(4); |
||||
buffer.writeBytes(attachment); |
||||
} |
||||
} |
||||
} |
||||
|
||||
private byte toChar(int number) { |
||||
return (byte) (number ^ 0x30); |
||||
} |
||||
|
||||
static final char[] DigitTens = {'0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '1', '1', '1', '1', |
||||
'1', '1', '1', '1', '1', '1', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '3', '3', '3', |
||||
'3', '3', '3', '3', '3', '3', '3', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '5', '5', |
||||
'5', '5', '5', '5', '5', '5', '5', '5', '6', '6', '6', '6', '6', '6', '6', '6', '6', '6', '7', |
||||
'7', '7', '7', '7', '7', '7', '7', '7', '7', '8', '8', '8', '8', '8', '8', '8', '8', '8', '8', |
||||
'9', '9', '9', '9', '9', '9', '9', '9', '9', '9',}; |
||||
|
||||
static final char[] DigitOnes = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', |
||||
'4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', |
||||
'3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', |
||||
'2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', |
||||
'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', |
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',}; |
||||
|
||||
static final char[] digits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', |
||||
'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', |
||||
'y', 'z'}; |
||||
|
||||
static final int[] sizeTable = {9, 99, 999, 9999, 99999, 999999, 9999999, 99999999, 999999999, |
||||
Integer.MAX_VALUE}; |
||||
|
||||
// Requires positive x
|
||||
static int stringSize(long x) { |
||||
for (int i = 0;; i++) |
||||
if (x <= sizeTable[i]) |
||||
return i + 1; |
||||
} |
||||
|
||||
static void getChars(long i, int index, byte[] buf) { |
||||
long q, r; |
||||
int charPos = index; |
||||
byte sign = 0; |
||||
|
||||
if (i < 0) { |
||||
sign = '-'; |
||||
i = -i; |
||||
} |
||||
|
||||
// Generate two digits per iteration
|
||||
while (i >= 65536) { |
||||
q = i / 100; |
||||
// really: r = i - (q * 100);
|
||||
r = i - ((q << 6) + (q << 5) + (q << 2)); |
||||
i = q; |
||||
buf[--charPos] = (byte) DigitOnes[(int)r]; |
||||
buf[--charPos] = (byte) DigitTens[(int)r]; |
||||
} |
||||
|
||||
// Fall thru to fast mode for smaller numbers
|
||||
// assert(i <= 65536, i);
|
||||
for (;;) { |
||||
q = (i * 52429) >>> (16 + 3); |
||||
r = i - ((q << 3) + (q << 1)); // r = i-(q*10) ...
|
||||
buf[--charPos] = (byte) digits[(int)r]; |
||||
i = q; |
||||
if (i == 0) |
||||
break; |
||||
} |
||||
if (sign != 0) { |
||||
buf[--charPos] = sign; |
||||
} |
||||
} |
||||
|
||||
public static byte[] toChars(long i) { |
||||
int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i); |
||||
byte[] buf = new byte[size]; |
||||
getChars(i, size, buf); |
||||
return buf; |
||||
} |
||||
|
||||
public static byte[] longToBytes(long number) { |
||||
// TODO optimize
|
||||
int length = (int)(Math.log10(number)+1); |
||||
byte[] res = new byte[length]; |
||||
int i = length; |
||||
while (number > 0) { |
||||
res[--i] = (byte) (number % 10); |
||||
number = number / 10; |
||||
} |
||||
return res; |
||||
} |
||||
|
||||
public void encodePacket(Packet packet, ByteBuf buffer, ByteBufAllocator allocator, boolean binary) throws IOException { |
||||
ByteBuf buf = buffer; |
||||
if (!binary) { |
||||
buf = allocateBuffer(allocator); |
||||
} |
||||
byte type = toChar(packet.getType().getValue()); |
||||
buf.writeByte(type); |
||||
|
||||
try { |
||||
switch (packet.getType()) { |
||||
|
||||
case PONG: { |
||||
buf.writeBytes(packet.getData().toString().getBytes(CharsetUtil.UTF_8)); |
||||
break; |
||||
} |
||||
|
||||
case OPEN: { |
||||
ByteBufOutputStream out = new ByteBufOutputStream(buf); |
||||
jsonSupport.writeValue(out, packet.getData()); |
||||
break; |
||||
} |
||||
|
||||
case MESSAGE: { |
||||
|
||||
ByteBuf encBuf = null; |
||||
|
||||
if (packet.getSubType() == PacketType.ERROR) { |
||||
encBuf = allocateBuffer(allocator); |
||||
|
||||
ByteBufOutputStream out = new ByteBufOutputStream(encBuf); |
||||
jsonSupport.writeValue(out, packet.getData()); |
||||
} |
||||
|
||||
if (packet.getSubType() == PacketType.EVENT |
||||
|| packet.getSubType() == PacketType.ACK) { |
||||
|
||||
List<Object> values = new ArrayList<Object>(); |
||||
if (packet.getSubType() == PacketType.EVENT) { |
||||
values.add(packet.getName()); |
||||
} |
||||
|
||||
encBuf = allocateBuffer(allocator); |
||||
|
||||
List<Object> args = packet.getData(); |
||||
values.addAll(args); |
||||
ByteBufOutputStream out = new ByteBufOutputStream(encBuf); |
||||
jsonSupport.writeValue(out, values); |
||||
|
||||
if (!jsonSupport.getArrays().isEmpty()) { |
||||
packet.initAttachments(jsonSupport.getArrays().size()); |
||||
for (byte[] array : jsonSupport.getArrays()) { |
||||
packet.addAttachment(Unpooled.wrappedBuffer(array)); |
||||
} |
||||
packet.setSubType(packet.getSubType() == PacketType.ACK |
||||
? PacketType.BINARY_ACK : PacketType.BINARY_EVENT); |
||||
} |
||||
} |
||||
|
||||
byte subType = toChar(packet.getSubType().getValue()); |
||||
buf.writeByte(subType); |
||||
|
||||
if (packet.hasAttachments()) { |
||||
byte[] ackId = toChars(packet.getAttachments().size()); |
||||
buf.writeBytes(ackId); |
||||
buf.writeByte('-'); |
||||
} |
||||
|
||||
if (packet.getSubType() == PacketType.CONNECT) { |
||||
if (!packet.getNsp().isEmpty()) { |
||||
buf.writeBytes(packet.getNsp().getBytes(CharsetUtil.UTF_8)); |
||||
} |
||||
} else { |
||||
if (!packet.getNsp().isEmpty()) { |
||||
buf.writeBytes(packet.getNsp().getBytes(CharsetUtil.UTF_8)); |
||||
buf.writeByte(','); |
||||
} |
||||
} |
||||
|
||||
if (packet.getAckId() != null) { |
||||
byte[] ackId = toChars(packet.getAckId()); |
||||
buf.writeBytes(ackId); |
||||
} |
||||
|
||||
if (encBuf != null) { |
||||
buf.writeBytes(encBuf); |
||||
encBuf.release(); |
||||
} |
||||
|
||||
break; |
||||
} |
||||
} |
||||
} finally { |
||||
// we need to write a buffer in any case
|
||||
if (!binary) { |
||||
buffer.writeByte(0); |
||||
int length = buf.writerIndex(); |
||||
buffer.writeBytes(longToBytes(length)); |
||||
buffer.writeByte(0xff); |
||||
buffer.writeBytes(buf); |
||||
|
||||
buf.release(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
public static int find(ByteBuf buffer, ByteBuf searchValue) { |
||||
for (int i = buffer.readerIndex(); i < buffer.readerIndex() + buffer.readableBytes(); i++) { |
||||
if (isValueFound(buffer, i, searchValue)) { |
||||
return i; |
||||
} |
||||
} |
||||
return -1; |
||||
} |
||||
|
||||
private static boolean isValueFound(ByteBuf buffer, int index, ByteBuf search) { |
||||
for (int i = 0; i < search.readableBytes(); i++) { |
||||
if (buffer.getByte(index + i) != search.getByte(i)) { |
||||
return false; |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,60 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.protocol; |
||||
|
||||
|
||||
public enum PacketType { |
||||
|
||||
OPEN(0), CLOSE(1), PING(2), PONG(3), MESSAGE(4), UPGRADE(5), NOOP(6), |
||||
|
||||
CONNECT(0, true), DISCONNECT(1, true), EVENT(2, true), ACK(3, true), ERROR(4, true), BINARY_EVENT(5, true), BINARY_ACK(6, true); |
||||
|
||||
public static final PacketType[] VALUES = values(); |
||||
private final int value; |
||||
private final boolean inner; |
||||
|
||||
PacketType(int value) { |
||||
this(value, false); |
||||
} |
||||
|
||||
PacketType(int value, boolean inner) { |
||||
this.value = value; |
||||
this.inner = inner; |
||||
} |
||||
|
||||
public int getValue() { |
||||
return value; |
||||
} |
||||
|
||||
public static PacketType valueOf(int value) { |
||||
for (PacketType type : VALUES) { |
||||
if (type.getValue() == value && !type.inner) { |
||||
return type; |
||||
} |
||||
} |
||||
throw new IllegalStateException(); |
||||
} |
||||
|
||||
public static PacketType valueOfInner(int value) { |
||||
for (PacketType type : VALUES) { |
||||
if (type.getValue() == value && type.inner) { |
||||
return type; |
||||
} |
||||
} |
||||
throw new IllegalArgumentException("Can't parse " + value); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,126 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.protocol; |
||||
|
||||
import io.netty.buffer.ByteBuf; |
||||
|
||||
public class UTF8CharsScanner { |
||||
|
||||
/** |
||||
* Lookup table used for determining which input characters need special |
||||
* handling when contained in text segment. |
||||
*/ |
||||
static final int[] sInputCodes; |
||||
static { |
||||
/* |
||||
* 96 would do for most cases (backslash is ascii 94) but if we want to |
||||
* do lookups by raw bytes it's better to have full table |
||||
*/ |
||||
int[] table = new int[256]; |
||||
// Control chars and non-space white space are not allowed unquoted
|
||||
for (int i = 0; i < 32; ++i) { |
||||
table[i] = -1; |
||||
} |
||||
// And then string end and quote markers are special too
|
||||
table['"'] = 1; |
||||
table['\\'] = 1; |
||||
sInputCodes = table; |
||||
} |
||||
|
||||
/** |
||||
* Additionally we can combine UTF-8 decoding info into similar data table. |
||||
*/ |
||||
static final int[] sInputCodesUtf8; |
||||
static { |
||||
int[] table = new int[sInputCodes.length]; |
||||
System.arraycopy(sInputCodes, 0, table, 0, sInputCodes.length); |
||||
for (int c = 128; c < 256; ++c) { |
||||
int code; |
||||
|
||||
// We'll add number of bytes needed for decoding
|
||||
if ((c & 0xE0) == 0xC0) { // 2 bytes (0x0080 - 0x07FF)
|
||||
code = 2; |
||||
} else if ((c & 0xF0) == 0xE0) { // 3 bytes (0x0800 - 0xFFFF)
|
||||
code = 3; |
||||
} else if ((c & 0xF8) == 0xF0) { |
||||
// 4 bytes; double-char with surrogates and all...
|
||||
code = 4; |
||||
} else { |
||||
// And -1 seems like a good "universal" error marker...
|
||||
code = -1; |
||||
} |
||||
table[c] = code; |
||||
} |
||||
sInputCodesUtf8 = table; |
||||
} |
||||
|
||||
private int getCharTailIndex(ByteBuf inputBuffer, int i) { |
||||
int c = (int) inputBuffer.getByte(i) & 0xFF; |
||||
switch (sInputCodesUtf8[c]) { |
||||
case 2: // 2-byte UTF
|
||||
i += 2; |
||||
break; |
||||
case 3: // 3-byte UTF
|
||||
i += 3; |
||||
break; |
||||
case 4: // 4-byte UTF
|
||||
i += 4; |
||||
break; |
||||
default: |
||||
i++; |
||||
break; |
||||
} |
||||
return i; |
||||
} |
||||
|
||||
public int getLength(ByteBuf inputBuffer, int start) { |
||||
int len = 0; |
||||
for (int i = start; i < inputBuffer.writerIndex();) { |
||||
i = getCharTailIndex(inputBuffer, i); |
||||
len++; |
||||
} |
||||
return len; |
||||
} |
||||
|
||||
public int getActualLength(ByteBuf inputBuffer, int length) { |
||||
int len = 0; |
||||
int start = inputBuffer.readerIndex(); |
||||
for (int i = inputBuffer.readerIndex(); i < inputBuffer.readableBytes() + inputBuffer.readerIndex();) { |
||||
i = getCharTailIndex(inputBuffer, i); |
||||
len++; |
||||
if (length == len) { |
||||
return i-start; |
||||
} |
||||
} |
||||
throw new IllegalStateException(); |
||||
} |
||||
|
||||
|
||||
public int findTailIndex(ByteBuf inputBuffer, int start, int end, |
||||
int charsToRead) { |
||||
int len = 0; |
||||
int i = start; |
||||
while (i < end) { |
||||
i = getCharTailIndex(inputBuffer, i); |
||||
len++; |
||||
if (charsToRead == len) { |
||||
break; |
||||
} |
||||
} |
||||
return i; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,36 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.scheduler; |
||||
|
||||
import io.netty.channel.ChannelHandlerContext; |
||||
|
||||
import java.util.concurrent.TimeUnit; |
||||
|
||||
public interface CancelableScheduler { |
||||
|
||||
void update(ChannelHandlerContext ctx); |
||||
|
||||
void cancel(SchedulerKey key); |
||||
|
||||
void scheduleCallback(SchedulerKey key, Runnable runnable, long delay, TimeUnit unit); |
||||
|
||||
void schedule(Runnable runnable, long delay, TimeUnit unit); |
||||
|
||||
void schedule(SchedulerKey key, Runnable runnable, long delay, TimeUnit unit); |
||||
|
||||
void shutdown(); |
||||
|
||||
} |
@ -0,0 +1,112 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.scheduler; |
||||
|
||||
import java.util.Map; |
||||
import java.util.concurrent.ThreadFactory; |
||||
import java.util.concurrent.TimeUnit; |
||||
|
||||
import io.netty.channel.ChannelHandlerContext; |
||||
import io.netty.util.HashedWheelTimer; |
||||
import io.netty.util.Timeout; |
||||
import io.netty.util.TimerTask; |
||||
import io.netty.util.internal.PlatformDependent; |
||||
|
||||
public class HashedWheelScheduler implements CancelableScheduler { |
||||
|
||||
private final Map<SchedulerKey, Timeout> scheduledFutures = PlatformDependent.newConcurrentHashMap(); |
||||
private final HashedWheelTimer executorService; |
||||
|
||||
public HashedWheelScheduler() { |
||||
executorService = new HashedWheelTimer(); |
||||
} |
||||
|
||||
public HashedWheelScheduler(ThreadFactory threadFactory) { |
||||
executorService = new HashedWheelTimer(threadFactory); |
||||
} |
||||
|
||||
private volatile ChannelHandlerContext ctx; |
||||
|
||||
@Override |
||||
public void update(ChannelHandlerContext ctx) { |
||||
this.ctx = ctx; |
||||
} |
||||
|
||||
@Override |
||||
public void cancel(SchedulerKey key) { |
||||
Timeout timeout = scheduledFutures.remove(key); |
||||
if (timeout != null) { |
||||
timeout.cancel(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void schedule(final Runnable runnable, long delay, TimeUnit unit) { |
||||
executorService.newTimeout(new TimerTask() { |
||||
@Override |
||||
public void run(Timeout timeout) throws Exception { |
||||
runnable.run(); |
||||
} |
||||
}, delay, unit); |
||||
} |
||||
|
||||
@Override |
||||
public void scheduleCallback(final SchedulerKey key, final Runnable runnable, long delay, TimeUnit unit) { |
||||
Timeout timeout = executorService.newTimeout(new TimerTask() { |
||||
@Override |
||||
public void run(Timeout timeout) throws Exception { |
||||
ctx.executor().execute(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
try { |
||||
runnable.run(); |
||||
} finally { |
||||
scheduledFutures.remove(key); |
||||
} |
||||
} |
||||
}); |
||||
} |
||||
}, delay, unit); |
||||
|
||||
if (!timeout.isExpired()) { |
||||
scheduledFutures.put(key, timeout); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void schedule(final SchedulerKey key, final Runnable runnable, long delay, TimeUnit unit) { |
||||
Timeout timeout = executorService.newTimeout(new TimerTask() { |
||||
@Override |
||||
public void run(Timeout timeout) throws Exception { |
||||
try { |
||||
runnable.run(); |
||||
} finally { |
||||
scheduledFutures.remove(key); |
||||
} |
||||
} |
||||
}, delay, unit); |
||||
|
||||
if (!timeout.isExpired()) { |
||||
scheduledFutures.put(key, timeout); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void shutdown() { |
||||
executorService.stop(); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,133 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
/** |
||||
* Modified version of HashedWheelScheduler specially for timeouts handling. |
||||
* Difference: |
||||
* - handling old timeout with same key after adding new one |
||||
* fixes multithreaded problem that appears in highly concurrent non-atomic sequence cancel() -> schedule() |
||||
* |
||||
* (c) Alim Akbashev, 2015-02-11 |
||||
*/ |
||||
|
||||
package com.fr.third.socketio.scheduler; |
||||
|
||||
import io.netty.channel.ChannelHandlerContext; |
||||
import io.netty.util.HashedWheelTimer; |
||||
import io.netty.util.Timeout; |
||||
import io.netty.util.TimerTask; |
||||
import io.netty.util.internal.PlatformDependent; |
||||
|
||||
import java.util.concurrent.ConcurrentMap; |
||||
import java.util.concurrent.ThreadFactory; |
||||
import java.util.concurrent.TimeUnit; |
||||
|
||||
public class HashedWheelTimeoutScheduler implements CancelableScheduler { |
||||
|
||||
private final ConcurrentMap<SchedulerKey, Timeout> scheduledFutures = PlatformDependent.newConcurrentHashMap(); |
||||
private final HashedWheelTimer executorService; |
||||
|
||||
private volatile ChannelHandlerContext ctx; |
||||
|
||||
public HashedWheelTimeoutScheduler() { |
||||
executorService = new HashedWheelTimer(); |
||||
} |
||||
|
||||
public HashedWheelTimeoutScheduler(ThreadFactory threadFactory) { |
||||
executorService = new HashedWheelTimer(threadFactory); |
||||
} |
||||
|
||||
@Override |
||||
public void update(ChannelHandlerContext ctx) { |
||||
this.ctx = ctx; |
||||
} |
||||
|
||||
@Override |
||||
public void cancel(SchedulerKey key) { |
||||
Timeout timeout = scheduledFutures.remove(key); |
||||
if (timeout != null) { |
||||
timeout.cancel(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void schedule(final Runnable runnable, long delay, TimeUnit unit) { |
||||
executorService.newTimeout(new TimerTask() { |
||||
@Override |
||||
public void run(Timeout timeout) throws Exception { |
||||
runnable.run(); |
||||
} |
||||
}, delay, unit); |
||||
} |
||||
|
||||
@Override |
||||
public void scheduleCallback(final SchedulerKey key, final Runnable runnable, long delay, TimeUnit unit) { |
||||
Timeout timeout = executorService.newTimeout(new TimerTask() { |
||||
@Override |
||||
public void run(Timeout timeout) throws Exception { |
||||
ctx.executor().execute(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
try { |
||||
runnable.run(); |
||||
} finally { |
||||
scheduledFutures.remove(key); |
||||
} |
||||
} |
||||
}); |
||||
} |
||||
}, delay, unit); |
||||
|
||||
replaceScheduledFuture(key, timeout); |
||||
} |
||||
|
||||
@Override |
||||
public void schedule(final SchedulerKey key, final Runnable runnable, long delay, TimeUnit unit) { |
||||
Timeout timeout = executorService.newTimeout(new TimerTask() { |
||||
@Override |
||||
public void run(Timeout timeout) throws Exception { |
||||
try { |
||||
runnable.run(); |
||||
} finally { |
||||
scheduledFutures.remove(key); |
||||
} |
||||
} |
||||
}, delay, unit); |
||||
|
||||
replaceScheduledFuture(key, timeout); |
||||
} |
||||
|
||||
@Override |
||||
public void shutdown() { |
||||
executorService.stop(); |
||||
} |
||||
|
||||
private void replaceScheduledFuture(final SchedulerKey key, final Timeout newTimeout) { |
||||
final Timeout oldTimeout; |
||||
|
||||
if (newTimeout.isExpired()) { |
||||
// no need to put already expired timeout to scheduledFutures map.
|
||||
// simply remove old timeout
|
||||
oldTimeout = scheduledFutures.remove(key); |
||||
} else { |
||||
oldTimeout = scheduledFutures.put(key, newTimeout); |
||||
} |
||||
|
||||
// if there was old timeout, cancel it
|
||||
if (oldTimeout != null) { |
||||
oldTimeout.cancel(); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,60 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.scheduler; |
||||
|
||||
|
||||
public class SchedulerKey { |
||||
|
||||
public enum Type {PING_TIMEOUT, ACK_TIMEOUT, UPGRADE_TIMEOUT}; |
||||
|
||||
private final Type type; |
||||
private final Object sessionId; |
||||
|
||||
public SchedulerKey(Type type, Object sessionId) { |
||||
this.type = type; |
||||
this.sessionId = sessionId; |
||||
} |
||||
|
||||
@Override |
||||
public int hashCode() { |
||||
final int prime = 31; |
||||
int result = 1; |
||||
result = prime * result |
||||
+ ((sessionId == null) ? 0 : sessionId.hashCode()); |
||||
result = prime * result + ((type == null) ? 0 : type.hashCode()); |
||||
return result; |
||||
} |
||||
|
||||
@Override |
||||
public boolean equals(Object obj) { |
||||
if (this == obj) |
||||
return true; |
||||
if (obj == null) |
||||
return false; |
||||
if (getClass() != obj.getClass()) |
||||
return false; |
||||
SchedulerKey other = (SchedulerKey) obj; |
||||
if (sessionId == null) { |
||||
if (other.sessionId != null) |
||||
return false; |
||||
} else if (!sessionId.equals(other.sessionId)) |
||||
return false; |
||||
if (type != other.type) |
||||
return false; |
||||
return true; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,41 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.store; |
||||
|
||||
import com.fr.third.socketio.store.pubsub.PubSubListener; |
||||
import com.fr.third.socketio.store.pubsub.PubSubMessage; |
||||
import com.fr.third.socketio.store.pubsub.PubSubStore; |
||||
import com.fr.third.socketio.store.pubsub.PubSubType; |
||||
|
||||
public class MemoryPubSubStore implements PubSubStore { |
||||
|
||||
@Override |
||||
public void publish(PubSubType type, PubSubMessage msg) { |
||||
} |
||||
|
||||
@Override |
||||
public <T extends PubSubMessage> void subscribe(PubSubType type, PubSubListener<T> listener, Class<T> clazz) { |
||||
} |
||||
|
||||
@Override |
||||
public void unsubscribe(PubSubType type) { |
||||
} |
||||
|
||||
@Override |
||||
public void shutdown() { |
||||
} |
||||
|
||||
} |
@ -0,0 +1,46 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.store; |
||||
|
||||
import io.netty.util.internal.PlatformDependent; |
||||
|
||||
import java.util.Map; |
||||
|
||||
public class MemoryStore implements Store { |
||||
|
||||
private final Map<String, Object> store = PlatformDependent.newConcurrentHashMap(); |
||||
|
||||
@Override |
||||
public void set(String key, Object value) { |
||||
store.put(key, value); |
||||
} |
||||
|
||||
@Override |
||||
public <T> T get(String key) { |
||||
return (T) store.get(key); |
||||
} |
||||
|
||||
@Override |
||||
public boolean has(String key) { |
||||
return store.containsKey(key); |
||||
} |
||||
|
||||
@Override |
||||
public void del(String key) { |
||||
store.remove(key); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,53 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.store; |
||||
|
||||
import com.fr.third.socketio.store.pubsub.BaseStoreFactory; |
||||
import com.fr.third.socketio.store.pubsub.PubSubStore; |
||||
import io.netty.util.internal.PlatformDependent; |
||||
|
||||
import java.util.Map; |
||||
import java.util.UUID; |
||||
|
||||
public class MemoryStoreFactory extends BaseStoreFactory { |
||||
|
||||
private final MemoryPubSubStore pubSubMemoryStore = new MemoryPubSubStore(); |
||||
|
||||
@Override |
||||
public Store createStore(UUID sessionId) { |
||||
return new MemoryStore(); |
||||
} |
||||
|
||||
@Override |
||||
public PubSubStore pubSubStore() { |
||||
return pubSubMemoryStore; |
||||
} |
||||
|
||||
@Override |
||||
public void shutdown() { |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return getClass().getSimpleName() + " (local session store only)"; |
||||
} |
||||
|
||||
@Override |
||||
public <K, V> Map<K, V> createMap(String name) { |
||||
return PlatformDependent.newConcurrentHashMap(); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,90 @@
|
||||
///**
|
||||
// * Copyright 2012 Nikita Koksharov
|
||||
// *
|
||||
// * Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// * you may not use this file except in compliance with the License.
|
||||
// * You may obtain a copy of the License at
|
||||
// *
|
||||
// * http://www.apache.org/licenses/LICENSE-2.0
|
||||
// *
|
||||
// * Unless required by applicable law or agreed to in writing, software
|
||||
// * distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// * See the License for the specific language governing permissions and
|
||||
// * limitations under the License.
|
||||
// */
|
||||
//package com.fr.third.socketio.store;
|
||||
//
|
||||
//import java.util.Queue;
|
||||
//import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
//import java.util.concurrent.ConcurrentMap;
|
||||
//
|
||||
//import com.fr.third.socketio.store.pubsub.PubSubListener;
|
||||
//import com.fr.third.socketio.store.pubsub.PubSubMessage;
|
||||
//import com.fr.third.socketio.store.pubsub.PubSubStore;
|
||||
//import com.fr.third.socketio.store.pubsub.PubSubType;
|
||||
//import org.redisson.api.RTopic;
|
||||
//import org.redisson.api.RedissonClient;
|
||||
//import org.redisson.api.listener.MessageListener;
|
||||
//
|
||||
//import io.netty.util.internal.PlatformDependent;
|
||||
//
|
||||
//public class RedissonPubSubStore implements PubSubStore {
|
||||
//
|
||||
// private final RedissonClient redissonPub;
|
||||
// private final RedissonClient redissonSub;
|
||||
// private final Long nodeId;
|
||||
//
|
||||
// private final ConcurrentMap<String, Queue<Integer>> map = PlatformDependent.newConcurrentHashMap();
|
||||
//
|
||||
// public RedissonPubSubStore(RedissonClient redissonPub, RedissonClient redissonSub, Long nodeId) {
|
||||
// this.redissonPub = redissonPub;
|
||||
// this.redissonSub = redissonSub;
|
||||
// this.nodeId = nodeId;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void publish(PubSubType type, PubSubMessage msg) {
|
||||
// msg.setNodeId(nodeId);
|
||||
// redissonPub.getTopic(type.toString()).publish(msg);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public <T extends PubSubMessage> void subscribe(PubSubType type, final PubSubListener<T> listener, Class<T> clazz) {
|
||||
// String name = type.toString();
|
||||
// RTopic<T> topic = redissonSub.getTopic(name);
|
||||
// int regId = topic.addListener(new MessageListener<T>() {
|
||||
// @Override
|
||||
// public void onMessage(String channel, T msg) {
|
||||
// if (!nodeId.equals(msg.getNodeId())) {
|
||||
// listener.onMessage(msg);
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// Queue<Integer> list = map.get(name);
|
||||
// if (list == null) {
|
||||
// list = new ConcurrentLinkedQueue<Integer>();
|
||||
// Queue<Integer> oldList = map.putIfAbsent(name, list);
|
||||
// if (oldList != null) {
|
||||
// list = oldList;
|
||||
// }
|
||||
// }
|
||||
// list.add(regId);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void unsubscribe(PubSubType type) {
|
||||
// String name = type.toString();
|
||||
// Queue<Integer> regIds = map.remove(name);
|
||||
// RTopic<Object> topic = redissonSub.getTopic(name);
|
||||
// for (Integer id : regIds) {
|
||||
// topic.removeListener(id);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void shutdown() {
|
||||
// }
|
||||
//
|
||||
//}
|
@ -0,0 +1,51 @@
|
||||
///**
|
||||
// * Copyright 2012 Nikita Koksharov
|
||||
// *
|
||||
// * Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// * you may not use this file except in compliance with the License.
|
||||
// * You may obtain a copy of the License at
|
||||
// *
|
||||
// * http://www.apache.org/licenses/LICENSE-2.0
|
||||
// *
|
||||
// * Unless required by applicable law or agreed to in writing, software
|
||||
// * distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// * See the License for the specific language governing permissions and
|
||||
// * limitations under the License.
|
||||
// */
|
||||
//package com.fr.third.socketio.store;
|
||||
//
|
||||
//import java.util.Map;
|
||||
//import java.util.UUID;
|
||||
//
|
||||
//import org.redisson.api.RedissonClient;
|
||||
//
|
||||
//public class RedissonStore implements Store {
|
||||
//
|
||||
// private final Map<String, Object> map;
|
||||
//
|
||||
// public RedissonStore(UUID sessionId, RedissonClient redisson) {
|
||||
// this.map = redisson.getMap(sessionId.toString());
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void set(String key, Object value) {
|
||||
// map.put(key, value);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public <T> T get(String key) {
|
||||
// return (T) map.get(key);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public boolean has(String key) {
|
||||
// return map.containsKey(key);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void del(String key) {
|
||||
// map.remove(key);
|
||||
// }
|
||||
//
|
||||
//}
|
@ -0,0 +1,76 @@
|
||||
///**
|
||||
// * Copyright 2012 Nikita Koksharov
|
||||
// *
|
||||
// * Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// * you may not use this file except in compliance with the License.
|
||||
// * You may obtain a copy of the License at
|
||||
// *
|
||||
// * http://www.apache.org/licenses/LICENSE-2.0
|
||||
// *
|
||||
// * Unless required by applicable law or agreed to in writing, software
|
||||
// * distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// * See the License for the specific language governing permissions and
|
||||
// * limitations under the License.
|
||||
// */
|
||||
//package com.fr.third.socketio.store;
|
||||
//
|
||||
//import java.util.Map;
|
||||
//import java.util.UUID;
|
||||
//
|
||||
//import com.fr.third.socketio.store.pubsub.BaseStoreFactory;
|
||||
//import com.fr.third.socketio.store.pubsub.PubSubStore;
|
||||
//import org.redisson.Redisson;
|
||||
//import org.redisson.api.RedissonClient;
|
||||
//
|
||||
//public class RedissonStoreFactory extends BaseStoreFactory {
|
||||
//
|
||||
// private final RedissonClient redisClient;
|
||||
// private final RedissonClient redisPub;
|
||||
// private final RedissonClient redisSub;
|
||||
//
|
||||
// private final PubSubStore pubSubStore;
|
||||
//
|
||||
// public RedissonStoreFactory() {
|
||||
// this(Redisson.create());
|
||||
// }
|
||||
//
|
||||
// public RedissonStoreFactory(RedissonClient redisson) {
|
||||
// this.redisClient = redisson;
|
||||
// this.redisPub = redisson;
|
||||
// this.redisSub = redisson;
|
||||
//
|
||||
// this.pubSubStore = new RedissonPubSubStore(redisPub, redisSub, getNodeId());
|
||||
// }
|
||||
//
|
||||
// public RedissonStoreFactory(Redisson redisClient, Redisson redisPub, Redisson redisSub) {
|
||||
// this.redisClient = redisClient;
|
||||
// this.redisPub = redisPub;
|
||||
// this.redisSub = redisSub;
|
||||
//
|
||||
// this.pubSubStore = new RedissonPubSubStore(redisPub, redisSub, getNodeId());
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public Store createStore(UUID sessionId) {
|
||||
// return new RedissonStore(sessionId, redisClient);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public PubSubStore pubSubStore() {
|
||||
// return pubSubStore;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void shutdown() {
|
||||
// redisClient.shutdown();
|
||||
// redisPub.shutdown();
|
||||
// redisSub.shutdown();
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public <K, V> Map<K, V> createMap(String name) {
|
||||
// return redisClient.getMap(name);
|
||||
// }
|
||||
//
|
||||
//}
|
@ -0,0 +1,29 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.store; |
||||
|
||||
|
||||
public interface Store { |
||||
|
||||
void set(String key, Object val); |
||||
|
||||
<T> T get(String key); |
||||
|
||||
boolean has(String key); |
||||
|
||||
void del(String key); |
||||
|
||||
} |
@ -0,0 +1,44 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.store; |
||||
|
||||
import java.util.Map; |
||||
import java.util.UUID; |
||||
|
||||
import com.fr.third.socketio.Disconnectable; |
||||
import com.fr.third.socketio.handler.AuthorizeHandler; |
||||
import com.fr.third.socketio.namespace.NamespacesHub; |
||||
import com.fr.third.socketio.protocol.JsonSupport; |
||||
import com.fr.third.socketio.store.pubsub.PubSubStore; |
||||
|
||||
/** |
||||
* |
||||
* Creates a client Store and PubSubStore |
||||
* |
||||
*/ |
||||
public interface StoreFactory extends Disconnectable { |
||||
|
||||
PubSubStore pubSubStore(); |
||||
|
||||
<K, V> Map<K, V> createMap(String name); |
||||
|
||||
Store createStore(UUID sessionId); |
||||
|
||||
void init(NamespacesHub namespacesHub, AuthorizeHandler authorizeHandler, JsonSupport jsonSupport); |
||||
|
||||
void shutdown(); |
||||
|
||||
} |
@ -0,0 +1,97 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.store.pubsub; |
||||
|
||||
import com.fr.third.socketio.handler.AuthorizeHandler; |
||||
import com.fr.third.socketio.handler.ClientHead; |
||||
import com.fr.third.socketio.namespace.NamespacesHub; |
||||
import com.fr.third.socketio.protocol.JsonSupport; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
import com.fr.third.socketio.store.StoreFactory; |
||||
|
||||
public abstract class BaseStoreFactory implements StoreFactory { |
||||
|
||||
private final Logger log = LoggerFactory.getLogger(getClass()); |
||||
|
||||
private Long nodeId = (long) (Math.random() * 1000000); |
||||
|
||||
protected Long getNodeId() { |
||||
return nodeId; |
||||
} |
||||
|
||||
@Override |
||||
public void init(final NamespacesHub namespacesHub, final AuthorizeHandler authorizeHandler, JsonSupport jsonSupport) { |
||||
pubSubStore().subscribe(PubSubType.DISCONNECT, new PubSubListener<DisconnectMessage>() { |
||||
@Override |
||||
public void onMessage(DisconnectMessage msg) { |
||||
log.debug("{} sessionId: {}", PubSubType.DISCONNECT, msg.getSessionId()); |
||||
} |
||||
}, DisconnectMessage.class); |
||||
|
||||
pubSubStore().subscribe(PubSubType.CONNECT, new PubSubListener<ConnectMessage>() { |
||||
@Override |
||||
public void onMessage(ConnectMessage msg) { |
||||
authorizeHandler.connect(msg.getSessionId()); |
||||
log.debug("{} sessionId: {}", PubSubType.CONNECT, msg.getSessionId()); |
||||
} |
||||
}, ConnectMessage.class); |
||||
|
||||
pubSubStore().subscribe(PubSubType.DISPATCH, new PubSubListener<DispatchMessage>() { |
||||
@Override |
||||
public void onMessage(DispatchMessage msg) { |
||||
String name = msg.getRoom(); |
||||
|
||||
namespacesHub.get(msg.getNamespace()).dispatch(name, msg.getPacket()); |
||||
log.debug("{} packet: {}", PubSubType.DISPATCH, msg.getPacket()); |
||||
} |
||||
}, DispatchMessage.class); |
||||
|
||||
pubSubStore().subscribe(PubSubType.JOIN, new PubSubListener<JoinLeaveMessage>() { |
||||
@Override |
||||
public void onMessage(JoinLeaveMessage msg) { |
||||
String name = msg.getRoom(); |
||||
|
||||
namespacesHub.get(msg.getNamespace()).join(name, msg.getSessionId()); |
||||
log.debug("{} sessionId: {}", PubSubType.JOIN, msg.getSessionId()); |
||||
} |
||||
}, JoinLeaveMessage.class); |
||||
|
||||
pubSubStore().subscribe(PubSubType.LEAVE, new PubSubListener<JoinLeaveMessage>() { |
||||
@Override |
||||
public void onMessage(JoinLeaveMessage msg) { |
||||
String name = msg.getRoom(); |
||||
|
||||
namespacesHub.get(msg.getNamespace()).leave(name, msg.getSessionId()); |
||||
log.debug("{} sessionId: {}", PubSubType.LEAVE, msg.getSessionId()); |
||||
} |
||||
}, JoinLeaveMessage.class); |
||||
} |
||||
|
||||
@Override |
||||
public abstract PubSubStore pubSubStore(); |
||||
|
||||
@Override |
||||
public void onDisconnect(ClientHead client) { |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return getClass().getSimpleName() + " (distributed session store, distributed publish/subscribe)"; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,38 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.store.pubsub; |
||||
|
||||
import java.util.UUID; |
||||
|
||||
public class ConnectMessage extends PubSubMessage { |
||||
|
||||
private static final long serialVersionUID = 3108918714495865101L; |
||||
|
||||
private UUID sessionId; |
||||
|
||||
public ConnectMessage() { |
||||
} |
||||
|
||||
public ConnectMessage(UUID sessionId) { |
||||
super(); |
||||
this.sessionId = sessionId; |
||||
} |
||||
|
||||
public UUID getSessionId() { |
||||
return sessionId; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,38 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.store.pubsub; |
||||
|
||||
import java.util.UUID; |
||||
|
||||
public class DisconnectMessage extends PubSubMessage { |
||||
|
||||
private static final long serialVersionUID = -2763553673397520368L; |
||||
|
||||
private UUID sessionId; |
||||
|
||||
public DisconnectMessage() { |
||||
} |
||||
|
||||
public DisconnectMessage(UUID sessionId) { |
||||
super(); |
||||
this.sessionId = sessionId; |
||||
} |
||||
|
||||
public UUID getSessionId() { |
||||
return sessionId; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,49 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.store.pubsub; |
||||
|
||||
import com.fr.third.socketio.protocol.Packet; |
||||
|
||||
public class DispatchMessage extends PubSubMessage { |
||||
|
||||
private static final long serialVersionUID = 6692047718303934349L; |
||||
|
||||
private String room; |
||||
private String namespace; |
||||
private Packet packet; |
||||
|
||||
public DispatchMessage() { |
||||
} |
||||
|
||||
public DispatchMessage(String room, Packet packet, String namespace) { |
||||
this.room = room; |
||||
this.packet = packet; |
||||
this.namespace = namespace; |
||||
} |
||||
|
||||
public String getNamespace() { |
||||
return namespace; |
||||
} |
||||
|
||||
public Packet getPacket() { |
||||
return packet; |
||||
} |
||||
|
||||
public String getRoom() { |
||||
return room; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,50 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.store.pubsub; |
||||
|
||||
import java.util.UUID; |
||||
|
||||
public class JoinLeaveMessage extends PubSubMessage { |
||||
|
||||
private static final long serialVersionUID = -944515928988033174L; |
||||
|
||||
private UUID sessionId; |
||||
private String namespace; |
||||
private String room; |
||||
|
||||
public JoinLeaveMessage() { |
||||
} |
||||
|
||||
public JoinLeaveMessage(UUID id, String room, String namespace) { |
||||
super(); |
||||
this.sessionId = id; |
||||
this.room = room; |
||||
this.namespace = namespace; |
||||
} |
||||
|
||||
public String getNamespace() { |
||||
return namespace; |
||||
} |
||||
|
||||
public UUID getSessionId() { |
||||
return sessionId; |
||||
} |
||||
|
||||
public String getRoom() { |
||||
return room; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,23 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.store.pubsub; |
||||
|
||||
|
||||
public interface PubSubListener<T> { |
||||
|
||||
void onMessage(T data); |
||||
|
||||
} |
@ -0,0 +1,34 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.store.pubsub; |
||||
|
||||
import java.io.Serializable; |
||||
|
||||
public abstract class PubSubMessage implements Serializable { |
||||
|
||||
private static final long serialVersionUID = -8789343104393884987L; |
||||
|
||||
private Long nodeId; |
||||
|
||||
public Long getNodeId() { |
||||
return nodeId; |
||||
} |
||||
|
||||
public void setNodeId(Long nodeId) { |
||||
this.nodeId = nodeId; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,29 @@
|
||||
/** |
||||
* Copyright 2012 Nikita Koksharov |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package com.fr.third.socketio.store.pubsub; |
||||
|
||||
|
||||
public interface PubSubStore { |
||||
|
||||
void publish(PubSubType type, PubSubMessage msg); |
||||
|
||||
<T extends PubSubMessage> void subscribe(PubSubType type, PubSubListener<T> listener, Class<T> clazz); |
||||
|
||||
void unsubscribe(PubSubType type); |
||||
|
||||
void shutdown(); |
||||
|
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue