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