diff --git a/README b/README index aee6566..ef49c8e 100644 --- a/README +++ b/README @@ -1 +1,28 @@ Java backend integration for Socket.IO library (http://socket.io/) + +To use in a jetty webapp, grant access in your jetty xml file to the jetty server class org.eclipse.jetty.server.HttpConnection + + + + + + + + -org.eclipse.jetty.server.HttpConnection + -org.eclipse.jetty.continuation. + -org.eclipse.jetty.jndi. + -org.eclipse.jetty.plus.jaas. + -org.eclipse.jetty.websocket. + -org.eclipse.jetty.servlet.DefaultServlet + org.eclipse.jetty. + + + + + +More info: + +mvn eclipse:eclipse # for import the project in eclipse +mvn install -Dlicense.skip=true # to install the jars in your repo + +Lauch from eclipse the Start* sample classes to test it. diff --git a/core/pom.xml b/core/pom.xml index f899f09..d432eca 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -19,10 +19,15 @@ - org.testatoo.container - testatoo-container-jetty + org.mortbay.jetty + servlet-api provided + + log4j + log4j + true + diff --git a/core/src/main/java/com/glines/socketio/annotation/Handle.java b/core/src/main/java/com/glines/socketio/annotation/Handle.java new file mode 100644 index 0000000..48d1933 --- /dev/null +++ b/core/src/main/java/com/glines/socketio/annotation/Handle.java @@ -0,0 +1,39 @@ +/** + * The MIT License + * Copyright (c) 2010 Tad Glines + * + * Contributors: Ovea.com, Mycila.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.glines.socketio.annotation; + +import com.glines.socketio.server.TransportType; + +import java.lang.annotation.*; + +/** + * @author Mathieu Carbou (mathieu.carbou@gmail.com) + */ +@Target(ElementType.TYPE) +@Inherited +@Retention(RetentionPolicy.RUNTIME) +public @interface Handle { + TransportType[] value() default {}; +} diff --git a/core/src/main/java/com/glines/socketio/client/common/SocketIOConnection.java b/core/src/main/java/com/glines/socketio/client/common/SocketIOConnection.java index a779f95..8cc11ef 100644 --- a/core/src/main/java/com/glines/socketio/client/common/SocketIOConnection.java +++ b/core/src/main/java/com/glines/socketio/client/common/SocketIOConnection.java @@ -2,6 +2,8 @@ * The MIT License * Copyright (c) 2010 Tad Glines * + * Contributors: Ovea.com, Mycila.com + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights @@ -28,17 +30,11 @@ public interface SocketIOConnection { interface Factory { - SocketIOConnection create(SocketIOConnection.SocketIOConnectionListener listener, + SocketIOConnection create(SocketIOConnectionListener listener, String host, short port); } - interface SocketIOConnectionListener { - public abstract void onConnect(); - public abstract void onDisconnect(DisconnectReason reason, String errorMessage); - public abstract void onMessage(int messageType, String message); - } - - /** + /** * Initiate a connection attempt. If the connection succeeds, then the * {@link SocketIOConnectionListener#onConnect() onConnect} will be called. If the connection * attempt fails, then @@ -81,7 +77,7 @@ interface SocketIOConnectionListener { * * @param message * @throws IllegalStateException if the socket is not CONNECTED. - * @throws SocketIOMessageParserException if the message type parser encode() failed. + * @throws SocketIOException if the message type parser encode() failed. */ void sendMessage(int messageType, String message) throws SocketIOException; } diff --git a/core/src/main/java/com/glines/socketio/client/common/SocketIOConnectionListener.java b/core/src/main/java/com/glines/socketio/client/common/SocketIOConnectionListener.java new file mode 100644 index 0000000..4f72ba7 --- /dev/null +++ b/core/src/main/java/com/glines/socketio/client/common/SocketIOConnectionListener.java @@ -0,0 +1,36 @@ +/** + * The MIT License + * Copyright (c) 2010 Tad Glines + * + * Contributors: Ovea.com, Mycila.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.glines.socketio.client.common; + +import com.glines.socketio.common.DisconnectReason; + +/** +* @author Mathieu Carbou (mathieu.carbou@gmail.com) +*/ +public interface SocketIOConnectionListener { + void onConnect(); + void onDisconnect(DisconnectReason reason, String errorMessage); + void onMessage(int messageType, String message); +} diff --git a/core/src/main/java/com/glines/socketio/common/ConnectionState.java b/core/src/main/java/com/glines/socketio/common/ConnectionState.java index 823a79f..301e224 100644 --- a/core/src/main/java/com/glines/socketio/common/ConnectionState.java +++ b/core/src/main/java/com/glines/socketio/common/ConnectionState.java @@ -2,6 +2,8 @@ * The MIT License * Copyright (c) 2010 Tad Glines * + * Contributors: Ovea.com, Mycila.com + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights diff --git a/core/src/main/java/com/glines/socketio/common/DisconnectReason.java b/core/src/main/java/com/glines/socketio/common/DisconnectReason.java index eb70844..20de289 100644 --- a/core/src/main/java/com/glines/socketio/common/DisconnectReason.java +++ b/core/src/main/java/com/glines/socketio/common/DisconnectReason.java @@ -2,6 +2,8 @@ * The MIT License * Copyright (c) 2010 Tad Glines * + * Contributors: Ovea.com, Mycila.com + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights @@ -30,7 +32,7 @@ public enum DisconnectReason { CLOSE_FAILED(4), // The connection dropped before an orderly close could complete. ERROR(5), // A GET or POST returned an error, or an internal error occurred. CLOSED_REMOTELY(6), // Remote end point initiated a close. - CLOSED(6); // Locally initiated close succeeded. + CLOSED(7); // Locally initiated close succeeded. private int value; private DisconnectReason(int v) { this.value = v; } diff --git a/core/src/main/java/com/glines/socketio/common/SocketIOException.java b/core/src/main/java/com/glines/socketio/common/SocketIOException.java index 220957f..694dcf9 100644 --- a/core/src/main/java/com/glines/socketio/common/SocketIOException.java +++ b/core/src/main/java/com/glines/socketio/common/SocketIOException.java @@ -2,6 +2,8 @@ * The MIT License * Copyright (c) 2010 Tad Glines * + * Contributors: Ovea.com, Mycila.com + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights diff --git a/core/src/main/java/com/glines/socketio/server/AbstractTransport.java b/core/src/main/java/com/glines/socketio/server/AbstractTransport.java new file mode 100644 index 0000000..daa1ec4 --- /dev/null +++ b/core/src/main/java/com/glines/socketio/server/AbstractTransport.java @@ -0,0 +1,75 @@ +/** + * The MIT License + * Copyright (c) 2010 Tad Glines + * + * Contributors: Ovea.com, Mycila.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.glines.socketio.server; + +import javax.servlet.ServletConfig; + +/** + * @author Mathieu Carbou + */ +public abstract class AbstractTransport implements Transport { + + private ServletConfig servletConfig; + private SocketIOConfig config; + private TransportHandlerProvider transportHandlerProvider; + + @Override + public void destroy() { + } + + @Override + public final void init(ServletConfig config) throws TransportInitializationException { + this.servletConfig = config; + this.config = new ServletBasedSocketIOConfig(servletConfig, getType().toString()); + init(); + } + + protected final ServletConfig getServletConfig() { + return servletConfig; + } + + protected final SocketIOConfig getConfig() { + return config; + } + + protected void init() throws TransportInitializationException { + } + + protected final TransportHandler newHandler(Class type, SocketIOSession session) { + TransportHandler handler = transportHandlerProvider.get(type, getType()); + handler.setSession(session); + return handler; + } + + @Override + public String toString() { + return getType().toString(); + } + + @Override + public final void setTransportHandlerProvider(TransportHandlerProvider transportHandlerProvider) { + this.transportHandlerProvider = transportHandlerProvider; + } +} diff --git a/core/src/main/java/com/glines/socketio/server/AbstractTransportHandler.java b/core/src/main/java/com/glines/socketio/server/AbstractTransportHandler.java new file mode 100644 index 0000000..0fe091d --- /dev/null +++ b/core/src/main/java/com/glines/socketio/server/AbstractTransportHandler.java @@ -0,0 +1,60 @@ +/** + * The MIT License + * Copyright (c) 2010 Tad Glines + * + * Contributors: Ovea.com, Mycila.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.glines.socketio.server; + +/** + * @author Mathieu Carbou + */ +public abstract class AbstractTransportHandler implements TransportHandler { + + private SocketIOConfig config; + private SocketIOSession session; + + @Override + public final void init(SocketIOConfig config) { + this.config = config; + init(); + } + + @Override + public void setSession(SocketIOSession session) { + this.session = session; + } + + protected final SocketIOConfig getConfig() { + return config; + } + + protected final SocketIOSession getSession() { + return session; + } + + protected void init() { + } + + @Override + public void disconnectWhenEmpty() { + } +} diff --git a/core/src/main/java/com/glines/socketio/server/AnnotationTransportHandlerProvider.java b/core/src/main/java/com/glines/socketio/server/AnnotationTransportHandlerProvider.java new file mode 100644 index 0000000..5f7bf31 --- /dev/null +++ b/core/src/main/java/com/glines/socketio/server/AnnotationTransportHandlerProvider.java @@ -0,0 +1,108 @@ +/** + * The MIT License + * Copyright (c) 2010 Tad Glines + * + * Contributors: Ovea.com, Mycila.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.glines.socketio.server; + +import com.glines.socketio.annotation.Handle; +import com.glines.socketio.util.DefaultLoader; +import com.glines.socketio.util.ServiceClassLoader; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.Collection; +import java.util.EnumMap; +import java.util.Iterator; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Mathieu Carbou (mathieu.carbou@gmail.com) + */ +public final class AnnotationTransportHandlerProvider implements TransportHandlerProvider { + + private static final Logger LOGGER = Logger.getLogger(AnnotationTransportHandlerProvider.class.getName()); + + private final Map> handlerClasses = new EnumMap>(TransportType.class); + + @Override + public TransportHandler get(Class handlerType, TransportType transportType) { + Class handlerClass = handlerClasses.get(transportType); + if (handlerClass == null) + throw new IllegalArgumentException("No TransportHandler found for transport " + transportType); + if (!handlerType.isAssignableFrom(handlerClass)) + throw new IllegalArgumentException("TransportHandler " + handlerClass.getName() + " is not of required type " + handlerType.getName() + " for transport " + transportType); + return load(handlerClass); + } + + @Override + public boolean isSupported(TransportType type) { + return handlerClasses.containsKey(type); + } + + @Override + public void init() { + handlerClasses.clear(); + ServiceClassLoader serviceClassLoader = ServiceClassLoader.load( + TransportHandler.class, + new DefaultLoader(Thread.currentThread().getContextClassLoader())); + Iterator> it = serviceClassLoader.iterator(); + while (it.hasNext()) { + Class transportHandlerClass; + try { + transportHandlerClass = it.next(); + } catch (Throwable e) { + LOGGER.log(Level.INFO, "Unable to load transport hander class. Error: " + e.getMessage(), e); + continue; + } + // try to load it to see if it is available + if (load(transportHandlerClass) != null) { + Handle handle = transportHandlerClass.getAnnotation(Handle.class); + if (handle != null) { + for (TransportType type : handle.value()) { + handlerClasses.put(type, transportHandlerClass); + } + } + } + } + } + + private static TransportHandler load(Class c) { + try { + Constructor ctor = c.getConstructor(); + return ctor.newInstance(); + } catch (InvocationTargetException e) { + LOGGER.log(Level.INFO, "Unable to load transport handler class " + c.getName() + ". Error: " + e.getTargetException().getMessage(), e.getTargetException()); + } catch (Exception e) { + LOGGER.log(Level.INFO, "Unable to load transport handler class " + c.getName() + ". Error: " + e.getMessage(), e); + } + return null; + } + + @Override + public Map> listAll() { + return handlerClasses; + } + +} diff --git a/core/src/main/java/com/glines/socketio/server/ClasspathTransportDiscovery.java b/core/src/main/java/com/glines/socketio/server/ClasspathTransportDiscovery.java new file mode 100644 index 0000000..62113fc --- /dev/null +++ b/core/src/main/java/com/glines/socketio/server/ClasspathTransportDiscovery.java @@ -0,0 +1,72 @@ +/** + * The MIT License + * Copyright (c) 2010 Tad Glines + * + * Contributors: Ovea.com, Mycila.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.glines.socketio.server; + +import com.glines.socketio.util.DefaultLoader; +import com.glines.socketio.util.ServiceClassLoader; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Mathieu Carbou + */ +public final class ClasspathTransportDiscovery implements TransportDiscovery { + + private static final Logger LOGGER = Logger.getLogger(ClasspathTransportDiscovery.class.getName()); + + @Override + public Iterable discover() { + List transports = new LinkedList(); + // discover transports lazily + ServiceClassLoader serviceClassLoader = ServiceClassLoader.load( + Transport.class, + new DefaultLoader(Thread.currentThread().getContextClassLoader())); + Iterator> it = serviceClassLoader.iterator(); + while (it.hasNext()) { + Class transportClass; + try { + transportClass = it.next(); + } catch (Throwable e) { + LOGGER.log(Level.INFO, "Unable to load transport class: Error: " + e.getMessage(), e); + continue; + } + try { + Constructor ctor = transportClass.getConstructor(); + transports.add(ctor.newInstance()); + } catch (InvocationTargetException e) { + LOGGER.log(Level.INFO, "Unable to load transport class " + transportClass.getName() + ". Error: " + e.getTargetException().getMessage(), e.getTargetException()); + } catch (Throwable e) { + LOGGER.log(Level.INFO, "Unable to load transport class " + transportClass.getName() + ". Error: " + e.getMessage(), e); + } + } + return transports; + } +} diff --git a/core/src/main/java/com/glines/socketio/server/ConnectableTransportHandler.java b/core/src/main/java/com/glines/socketio/server/ConnectableTransportHandler.java new file mode 100644 index 0000000..14c1f94 --- /dev/null +++ b/core/src/main/java/com/glines/socketio/server/ConnectableTransportHandler.java @@ -0,0 +1,39 @@ +/** + * The MIT License + * Copyright (c) 2010 Tad Glines + * + * Contributors: Ovea.com, Mycila.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.glines.socketio.server; + +import com.glines.socketio.server.transport.DataHandler; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * @author Mathieu Carbou (mathieu.carbou@gmail.com) + */ +public interface ConnectableTransportHandler { + void connect(HttpServletRequest request, HttpServletResponse response) throws IOException; + void setDataHandler(DataHandler dataHandler); +} diff --git a/core/src/main/java/com/glines/socketio/server/DefaultSession.java b/core/src/main/java/com/glines/socketio/server/DefaultSession.java new file mode 100644 index 0000000..c893655 --- /dev/null +++ b/core/src/main/java/com/glines/socketio/server/DefaultSession.java @@ -0,0 +1,361 @@ +/** + * The MIT License + * Copyright (c) 2010 Tad Glines + * + * Contributors: Ovea.com, Mycila.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.glines.socketio.server; + +import com.glines.socketio.common.ConnectionState; +import com.glines.socketio.common.DisconnectReason; +import com.glines.socketio.common.SocketIOException; +import com.glines.socketio.util.Web; + +import java.util.Map; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicLong; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Mathieu Carbou (mathieu.carbou@gmail.com) + */ +class DefaultSession implements SocketIOSession { + + private static final int SESSION_ID_LENGTH = 20; + private static final Logger LOGGER = Logger.getLogger(DefaultSession.class.getName()); + + private final SocketIOSessionManager socketIOSessionManager; + private final String sessionId = Web.generateRandomString(SESSION_ID_LENGTH); + private final AtomicLong messageId = new AtomicLong(0); + private final Map attributes = new ConcurrentHashMap(); + + private SocketIOInbound inbound; + private TransportHandler handler; + private ConnectionState state = ConnectionState.CONNECTING; + private long hbDelay; + private SessionTask hbDelayTask; + private long timeout; + private SessionTask timeoutTask; + private boolean timedout; + private String closeId; + + DefaultSession(SocketIOSessionManager socketIOSessionManager, SocketIOInbound inbound) { + this.socketIOSessionManager = socketIOSessionManager; + this.inbound = inbound; + } + + @Override + public void setAttribute(String key, Object val) { + attributes.put(key, val); + } + + @Override + public Object getAttribute(String key) { + return attributes.get(key); + } + + @Override + public String getSessionId() { + return sessionId; + } + + @Override + public ConnectionState getConnectionState() { + return state; + } + + @Override + public SocketIOInbound getInbound() { + return inbound; + } + + @Override + public TransportHandler getTransportHandler() { + return handler; + } + + private void onTimeout() { + if (LOGGER.isLoggable(Level.FINE)) + LOGGER.log(Level.FINE, "Session[" + sessionId + "]: onTimeout"); + if (!timedout) { + timedout = true; + state = ConnectionState.CLOSED; + onDisconnect(DisconnectReason.TIMEOUT); + handler.abort(); + } + } + + @Override + public void startTimeoutTimer() { + clearTimeoutTimer(); + if (!timedout && timeout > 0) { + timeoutTask = scheduleTask(new Runnable() { + @Override + public void run() { + DefaultSession.this.onTimeout(); + } + }, timeout); + } + } + + @Override + public void clearTimeoutTimer() { + if (timeoutTask != null) { + timeoutTask.cancel(); + timeoutTask = null; + } + } + + private void sendPing() { + String data = "" + messageId.incrementAndGet(); + if (LOGGER.isLoggable(Level.FINE)) + LOGGER.log(Level.FINE, "Session[" + sessionId + "]: sendPing " + data); + try { + handler.sendMessage(new SocketIOFrame(SocketIOFrame.FrameType.PING, 0, data)); + } catch (SocketIOException e) { + if (LOGGER.isLoggable(Level.FINE)) + LOGGER.log(Level.FINE, "handler.sendMessage failed: ", e); + handler.abort(); + } + startTimeoutTimer(); + } + + @Override + public void startHeartbeatTimer() { + clearHeartbeatTimer(); + if (!timedout && hbDelay > 0) { + hbDelayTask = scheduleTask(new Runnable() { + @Override + public void run() { + sendPing(); + } + }, hbDelay); + } + } + + @Override + public void clearHeartbeatTimer() { + if (hbDelayTask != null) { + hbDelayTask.cancel(); + hbDelayTask = null; + } + } + + @Override + public void setHeartbeat(long delay) { + hbDelay = delay; + } + + @Override + public long getHeartbeat() { + return hbDelay; + } + + @Override + public void setTimeout(long timeout) { + this.timeout = timeout; + } + + @Override + public long getTimeout() { + return timeout; + } + + @Override + public void startClose() { + state = ConnectionState.CLOSING; + closeId = "server"; + try { + handler.sendMessage(new SocketIOFrame(SocketIOFrame.FrameType.CLOSE, 0, closeId)); + } catch (SocketIOException e) { + if (LOGGER.isLoggable(Level.FINE)) + LOGGER.log(Level.FINE, "handler.sendMessage failed: ", e); + handler.abort(); + } + } + + @Override + public void onMessage(SocketIOFrame message) { + switch (message.getFrameType()) { + case SESSION_ID: + case HEARTBEAT_INTERVAL: + // Ignore these two messages types as they are only intended to be from server to client. + break; + case CLOSE: + if (LOGGER.isLoggable(Level.FINE)) + LOGGER.log(Level.FINE, "Session[" + sessionId + "]: onClose: " + message.getData()); + onClose(message.getData()); + break; + case PING: + if (LOGGER.isLoggable(Level.FINE)) + LOGGER.log(Level.FINE, "Session[" + sessionId + "]: onPing: " + message.getData()); + onPing(message.getData()); + break; + case PONG: + if (LOGGER.isLoggable(Level.FINE)) + LOGGER.log(Level.FINE, "Session[" + sessionId + "]: onPong: " + message.getData()); + onPong(message.getData()); + break; + case DATA: + if (LOGGER.isLoggable(Level.FINE)) + LOGGER.log(Level.FINE, "Session[" + sessionId + "]: onMessage: " + message.getData()); + onMessage(message.getData()); + break; + default: + // Ignore unknown message types + break; + } + } + + @Override + public void onPing(String data) { + try { + handler.sendMessage(new SocketIOFrame(SocketIOFrame.FrameType.PONG, 0, data)); + } catch (SocketIOException e) { + if (LOGGER.isLoggable(Level.FINE)) + LOGGER.log(Level.FINE, "handler.sendMessage failed: ", e); + handler.abort(); + } + } + + @Override + public void onPong(String data) { + clearTimeoutTimer(); + } + + @Override + public void onClose(String data) { + if (state == ConnectionState.CLOSING) { + if (closeId != null && closeId.equals(data)) { + state = ConnectionState.CLOSED; + onDisconnect(DisconnectReason.CLOSED); + handler.abort(); + } else { + try { + handler.sendMessage(new SocketIOFrame(SocketIOFrame.FrameType.CLOSE, 0, data)); + } catch (SocketIOException e) { + if (LOGGER.isLoggable(Level.FINE)) + LOGGER.log(Level.FINE, "handler.sendMessage failed: ", e); + handler.abort(); + } + } + } else { + state = ConnectionState.CLOSING; + try { + handler.sendMessage(new SocketIOFrame(SocketIOFrame.FrameType.CLOSE, 0, data)); + handler.disconnectWhenEmpty(); + if ("client".equals(data)) + onDisconnect(DisconnectReason.CLOSED_REMOTELY); + } catch (SocketIOException e) { + if (LOGGER.isLoggable(Level.FINE)) + LOGGER.log(Level.FINE, "handler.sendMessage failed: ", e); + handler.abort(); + } + } + } + + @Override + public SessionTask scheduleTask(Runnable task, long delay) { + final Future future = socketIOSessionManager.executor.schedule(task, delay, TimeUnit.MILLISECONDS); + return new SessionTask() { + @Override + public boolean cancel() { + return future.cancel(false); + } + }; + } + + @Override + public void onConnect(TransportHandler handler) { + if (handler == null) { + state = ConnectionState.CLOSED; + inbound = null; + socketIOSessionManager.socketIOSessions.remove(sessionId); + } else if (this.handler == null) { + this.handler = handler; + if (inbound == null) { + state = ConnectionState.CLOSED; + handler.abort(); + } else { + try { + state = ConnectionState.CONNECTED; + inbound.onConnect(handler); + } catch (Throwable e) { + if (LOGGER.isLoggable(Level.WARNING)) + LOGGER.log(Level.WARNING, "Session[" + sessionId + "]: Exception thrown by SocketIOInbound.onConnect()", e); + state = ConnectionState.CLOSED; + handler.abort(); + } + } + } else { + handler.abort(); + } + } + + @Override + public void onMessage(String message) { + if (inbound != null) { + try { + inbound.onMessage(SocketIOFrame.TEXT_MESSAGE_TYPE, message); + } catch (Throwable e) { + if (LOGGER.isLoggable(Level.WARNING)) + LOGGER.log(Level.WARNING, "Session[" + sessionId + "]: Exception thrown by SocketIOInbound.onMessage()", e); + } + } + } + + @Override + public void onDisconnect(DisconnectReason reason) { + if (LOGGER.isLoggable(Level.FINE)) + LOGGER.log(Level.FINE, "Session[" + sessionId + "]: onDisconnect: " + reason); + clearTimeoutTimer(); + clearHeartbeatTimer(); + if (inbound != null) { + state = ConnectionState.CLOSED; + try { + inbound.onDisconnect(reason, null); + } catch (Throwable e) { + if (LOGGER.isLoggable(Level.WARNING)) + LOGGER.log(Level.WARNING, "Session[" + sessionId + "]: Exception thrown by SocketIOInbound.onDisconnect()", e); + } + inbound = null; + } + } + + @Override + public void onShutdown() { + if (LOGGER.isLoggable(Level.FINE)) + LOGGER.log(Level.FINE, "Session[" + sessionId + "]: onShutdown"); + if (inbound != null) { + if (state == ConnectionState.CLOSING) { + if (closeId != null) { + onDisconnect(DisconnectReason.CLOSE_FAILED); + } else { + onDisconnect(DisconnectReason.CLOSED_REMOTELY); + } + } else { + onDisconnect(DisconnectReason.ERROR); + } + } + socketIOSessionManager.socketIOSessions.remove(sessionId); + } +} diff --git a/core/src/main/java/com/glines/socketio/server/ServletBasedSocketIOConfig.java b/core/src/main/java/com/glines/socketio/server/ServletBasedSocketIOConfig.java new file mode 100644 index 0000000..7672617 --- /dev/null +++ b/core/src/main/java/com/glines/socketio/server/ServletBasedSocketIOConfig.java @@ -0,0 +1,144 @@ +/** + * The MIT License + * Copyright (c) 2010 Tad Glines + * + * Contributors: Ovea.com, Mycila.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.glines.socketio.server; + +import javax.servlet.ServletConfig; +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Mathieu Carbou + */ +public final class ServletBasedSocketIOConfig implements SocketIOConfig { + + private static final Logger LOGGER = Logger.getLogger(ServletBasedSocketIOConfig.class.getName()); + private static final String KEY_TRANSPORT = SocketIOConfig.class.getName() + ".TRANSPORTS"; + + private final ServletConfig config; + private final String namespace; + + public ServletBasedSocketIOConfig(ServletConfig config, String namespace) { + this.namespace = namespace; + this.config = config; + } + + @Override + public long getHeartbeatDelay(long def) { + return getLong(PARAM_HEARTBEAT_DELAY, def); + } + + @Override + public long getHeartbeatTimeout(long def) { + return getLong(PARAM_HEARTBEAT_TIMEOUT, def); + } + + @Override + public long getTimeout(long def) { + return getLong(PARAM_TIMEOUT, def); + } + + @Override + public int getBufferSize() { + return getInt(PARAM_BUFFER_SIZE, DEFAULT_BUFFER_SIZE); + } + + @Override + public int getMaxIdle() { + return getInt(PARAM_MAX_IDLE, DEFAULT_MAX_IDLE); + } + + @Override + public void addTransport(Transport transport) { + getTransportMap().put(transport.getType(), transport); + } + + @Override + public Collection getTransports() { + return getTransportMap().values(); + } + + @Override + public Transport getTransport(TransportType type) { + return getTransportMap().get(type); + } + + @Override + public void removeTransport(TransportType type) { + getTransportMap().remove(type); + } + + @Override + public Transport getWebSocketTransport() { + return getTransport(TransportType.WEB_SOCKET); + } + + @Override + public int getInt(String param, int def) { + String v = getString(param); + return v == null ? def : Integer.parseInt(v); + } + + @Override + public long getLong(String param, long def) { + String v = getString(param); + return v == null ? def : Long.parseLong(v); + } + + @Override + public String getNamespace() { + return namespace; + } + + @Override + public String getString(String param) { + if (LOGGER.isLoggable(Level.FINE)) + LOGGER.fine("Getting InitParameter: " + namespace + "." + param); + String v = config.getInitParameter(namespace + "." + param); + if (v == null) { + if (LOGGER.isLoggable(Level.FINE)) + LOGGER.fine("Fallback to InitParameter: " + param); + v = config.getInitParameter(param); + } + return v; + } + + @Override + public String getString(String param, String def) { + String v = getString(param); + return v == null ? def : v; + } + + @SuppressWarnings({"unchecked"}) + private Map getTransportMap() { + Map transports = (Map) config.getServletContext().getAttribute(KEY_TRANSPORT); + if (transports == null) + config.getServletContext().setAttribute(KEY_TRANSPORT, transports = new ConcurrentHashMap()); + return transports; + } + +} diff --git a/core/src/main/java/com/glines/socketio/server/SessionManager.java b/core/src/main/java/com/glines/socketio/server/SessionManager.java new file mode 100644 index 0000000..a903356 --- /dev/null +++ b/core/src/main/java/com/glines/socketio/server/SessionManager.java @@ -0,0 +1,33 @@ +/** + * The MIT License + * Copyright (c) 2010 Tad Glines + * + * Contributors: Ovea.com, Mycila.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.glines.socketio.server; + +/** +* @author Mathieu Carbou (mathieu.carbou@gmail.com) +*/ +public interface SessionManager { + SocketIOSession createSession(SocketIOInbound inbound); + SocketIOSession getSession(String sessionId); +} diff --git a/core/src/main/java/com/glines/socketio/server/SocketIOClosedException.java b/core/src/main/java/com/glines/socketio/server/SocketIOClosedException.java index c8ae5d8..3e14dce 100644 --- a/core/src/main/java/com/glines/socketio/server/SocketIOClosedException.java +++ b/core/src/main/java/com/glines/socketio/server/SocketIOClosedException.java @@ -2,6 +2,8 @@ * The MIT License * Copyright (c) 2010 Tad Glines * + * Contributors: Ovea.com, Mycila.com + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights diff --git a/core/src/main/java/com/glines/socketio/server/SocketIOConfig.java b/core/src/main/java/com/glines/socketio/server/SocketIOConfig.java new file mode 100644 index 0000000..b4e882b --- /dev/null +++ b/core/src/main/java/com/glines/socketio/server/SocketIOConfig.java @@ -0,0 +1,62 @@ +/** + * The MIT License + * Copyright (c) 2010 Tad Glines + * + * Contributors: Ovea.com, Mycila.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.glines.socketio.server; + +import java.util.Collection; + +/** + * @author Mathieu Carbou + */ +public interface SocketIOConfig { + + String PARAM_HEARTBEAT_DELAY = "heartbeat-delay"; + String PARAM_HEARTBEAT_TIMEOUT = "heartbeat-timeout"; + String PARAM_TIMEOUT = "timeout"; + + String PARAM_BUFFER_SIZE = "bufferSize"; + String PARAM_MAX_IDLE = "maxIdleTime"; + + int DEFAULT_BUFFER_SIZE = 8192; + int DEFAULT_MAX_IDLE = 300 * 1000; + + long getHeartbeatDelay(long def); + long getHeartbeatTimeout(long def); + long getTimeout(long def); + int getBufferSize(); + int getMaxIdle(); + + void addTransport(Transport transport); + Collection getTransports(); + Transport getTransport(TransportType type); + void removeTransport(TransportType type); + Transport getWebSocketTransport(); + + String getString(String key); + String getString(String key, String def); + int getInt(String key, int def); + long getLong(String key, long def); + + String getNamespace(); +} diff --git a/core/src/main/java/com/glines/socketio/server/SocketIOFrame.java b/core/src/main/java/com/glines/socketio/server/SocketIOFrame.java index c37fc59..83a85b1 100644 --- a/core/src/main/java/com/glines/socketio/server/SocketIOFrame.java +++ b/core/src/main/java/com/glines/socketio/server/SocketIOFrame.java @@ -2,6 +2,8 @@ * The MIT License * Copyright (c) 2010 Tad Glines * + * Contributors: Ovea.com, Mycila.com + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights diff --git a/core/src/main/java/com/glines/socketio/server/SocketIOInbound.java b/core/src/main/java/com/glines/socketio/server/SocketIOInbound.java index dc2cfb6..62275ea 100644 --- a/core/src/main/java/com/glines/socketio/server/SocketIOInbound.java +++ b/core/src/main/java/com/glines/socketio/server/SocketIOInbound.java @@ -2,6 +2,8 @@ * The MIT License * Copyright (c) 2010 Tad Glines * + * Contributors: Ovea.com, Mycila.com + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights diff --git a/core/src/main/java/com/glines/socketio/server/SocketIOOutbound.java b/core/src/main/java/com/glines/socketio/server/SocketIOOutbound.java index ef9725f..6ca17bc 100644 --- a/core/src/main/java/com/glines/socketio/server/SocketIOOutbound.java +++ b/core/src/main/java/com/glines/socketio/server/SocketIOOutbound.java @@ -2,6 +2,8 @@ * The MIT License * Copyright (c) 2010 Tad Glines * + * Contributors: Ovea.com, Mycila.com + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights diff --git a/core/src/main/java/com/glines/socketio/server/SocketIOServlet.java b/core/src/main/java/com/glines/socketio/server/SocketIOServlet.java index 8cc4375..dd523c5 100644 --- a/core/src/main/java/com/glines/socketio/server/SocketIOServlet.java +++ b/core/src/main/java/com/glines/socketio/server/SocketIOServlet.java @@ -2,6 +2,8 @@ * The MIT License * Copyright (c) 2010 Tad Glines * + * Contributors: Ovea.com, Mycila.com + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights @@ -22,119 +24,123 @@ */ package com.glines.socketio.server; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.HashMap; -import java.util.Map; +import com.glines.socketio.util.IO; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.logging.Level; +import java.util.logging.Logger; + +public abstract class SocketIOServlet extends HttpServlet { -import org.eclipse.jetty.util.IO; + private static final Logger LOGGER = Logger.getLogger(SocketIOServlet.class.getName()); + private static final long serialVersionUID = 2L; -import com.glines.socketio.server.transport.FlashSocketTransport; -import com.glines.socketio.server.transport.HTMLFileTransport; -import com.glines.socketio.server.transport.JSONPPollingTransport; -import com.glines.socketio.server.transport.WebSocketTransport; -import com.glines.socketio.server.transport.XHRMultipartTransport; -import com.glines.socketio.server.transport.XHRPollingTransport; + private final SocketIOSessionManager sessionManager = new SocketIOSessionManager(); + private final TransportHandlerProvider transportHandlerProvider = new AnnotationTransportHandlerProvider(); -/** - */ -public abstract class SocketIOServlet extends HttpServlet { - public static final String BUFFER_SIZE_INIT_PARAM = "bufferSize"; - public static final String MAX_IDLE_TIME_INIT_PARAM = "maxIdleTime"; - public static final int BUFFER_SIZE_DEFAULT = 8192; - public static final int MAX_IDLE_TIME_DEFAULT = 300*1000; - private static final long serialVersionUID = 1L; - private SocketIOSessionManager sessionManager = null; - private Map transports = new HashMap(); - - @Override - public void init() throws ServletException { - super.init(); - String str = this.getInitParameter(BUFFER_SIZE_INIT_PARAM); - int bufferSize = str==null ? BUFFER_SIZE_DEFAULT : Integer.parseInt(str); - str = this.getInitParameter(MAX_IDLE_TIME_INIT_PARAM); - int maxIdleTime = str==null ? MAX_IDLE_TIME_DEFAULT : Integer.parseInt(str); - - sessionManager = new SocketIOSessionManager(); - WebSocketTransport websocketTransport = new WebSocketTransport(bufferSize, maxIdleTime); - FlashSocketTransport flashsocketTransport = new FlashSocketTransport(bufferSize, maxIdleTime); - HTMLFileTransport htmlFileTransport = new HTMLFileTransport(bufferSize, maxIdleTime); - XHRMultipartTransport xhrMultipartTransport = new XHRMultipartTransport(bufferSize, maxIdleTime); - XHRPollingTransport xhrPollingTransport = new XHRPollingTransport(bufferSize, maxIdleTime); - JSONPPollingTransport jsonpPollingTransport = new JSONPPollingTransport(bufferSize, maxIdleTime); - transports.put(websocketTransport.getName(), websocketTransport); - transports.put(flashsocketTransport.getName(), flashsocketTransport); - transports.put(htmlFileTransport.getName(), htmlFileTransport); - transports.put(xhrMultipartTransport.getName(), xhrMultipartTransport); - transports.put(xhrPollingTransport.getName(), xhrPollingTransport); - transports.put(jsonpPollingTransport.getName(), jsonpPollingTransport); - - for (Transport t: transports.values()) { - t.init(this.getServletConfig()); - } - } + private SocketIOConfig config; @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - serve(req, resp); + public void init() throws ServletException { + config = new ServletBasedSocketIOConfig(getServletConfig(), "socketio"); + + // lazy load available transport handlers + transportHandlerProvider.init(); + if (LOGGER.isLoggable(Level.INFO)) + LOGGER.log(Level.INFO, "Transport handlers loaded: " + transportHandlerProvider.listAll()); + + // lazily load available transports + TransportDiscovery transportDiscovery = new ClasspathTransportDiscovery(); + for (Transport transport : transportDiscovery.discover()) { + if (transportHandlerProvider.isSupported(transport.getType())) { + transport.setTransportHandlerProvider(transportHandlerProvider); + config.addTransport(transport); + } else { + LOGGER.log(Level.WARNING, "Transport " + transport.getType() + " ignored since not supported by any TransportHandler"); + } + } + // initialize them + for (Transport t : config.getTransports()) { + try { + t.init(getServletConfig()); + } catch (TransportInitializationException e) { + config.removeTransport(t.getType()); + LOGGER.log(Level.WARNING, "Transport " + t.getType() + " disabled. Initialization failed: " + e.getMessage()); + } + } + if (LOGGER.isLoggable(Level.INFO)) + LOGGER.log(Level.INFO, "Transports loaded: " + config.getTransports()); } - @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - serve(req, resp); - } - - private void serve(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + @Override + public void destroy() { + for (Transport t : config.getTransports()) { + t.destroy(); + } + super.destroy(); + } - String path = request.getPathInfo(); - if (path == null || path.length() == 0 || "/".equals(path)) { - response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Missing SocketIO transport"); - return; - } - if (path.startsWith("/")) path = path.substring(1); - String[] parts = path.split("/"); - - Transport transport = transports.get(parts[0]); - if (transport == null) { - if ("GET".equals(request.getMethod()) && "socket.io.js".equals(parts[0])) { - response.setContentType("text/javascript"); - InputStream is = this.getClass().getClassLoader().getResourceAsStream("com/glines/socketio/socket.io.js"); - OutputStream os = response.getOutputStream(); - IO.copy(is, os); - return; - } else { - response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Unknown SocketIO transport"); - return; - } - } - - transport.handle(request, response, new Transport.InboundFactory() { - - @Override - public SocketIOInbound getInbound(HttpServletRequest request) { - return SocketIOServlet.this.doSocketIOConnect(request); - } - - }, sessionManager); + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + serve(req, resp); } @Override - public void destroy() { - for (Transport t: transports.values()) { - t.destroy(); - } - super.destroy(); + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + serve(req, resp); } /** * Returns an instance of SocketIOInbound or null if the connection is to be denied. * The value of cookies and protocols may be null. */ - protected abstract SocketIOInbound doSocketIOConnect(HttpServletRequest request); + protected abstract SocketIOInbound doSocketIOConnect(HttpServletRequest request); + + private void serve(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + + String path = request.getPathInfo(); + if (path == null || path.length() == 0 || "/".equals(path)) { + response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Missing SocketIO transport"); + return; + } + if (path.startsWith("/")) path = path.substring(1); + String[] parts = path.split("/"); + + Transport transport = config.getTransport(TransportType.from(parts[0])); + if (transport == null) { + if ("GET".equals(request.getMethod()) && "socket.io.js".equals(parts[0])) { + response.setContentType("text/javascript"); + InputStream is = this.getClass().getClassLoader().getResourceAsStream("com/glines/socketio/socket.io.js"); + OutputStream os = response.getOutputStream(); + IO.copy(is, os); + return; + }else if ("GET".equals(request.getMethod()) && "WebSocketMain.swf".equals(parts[0])) { + response.setContentType("application/x-shockwave-flash"); + InputStream is = this.getClass().getClassLoader().getResourceAsStream("com/glines/socketio/WebSocketMain.swf"); + OutputStream os = response.getOutputStream(); + IO.copy(is, os); + return; + } else { + response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Unknown SocketIO transport: " + parts[0]); + return; + } + } + + if (LOGGER.isLoggable(Level.FINE)) + LOGGER.log(Level.FINE, "Handling request from " + request.getRemoteHost() + ":" + request.getRemotePort() + " with transport: " + transport.getType()); + + transport.handle(request, response, new Transport.InboundFactory() { + @Override + public SocketIOInbound getInbound(HttpServletRequest request) { + return SocketIOServlet.this.doSocketIOConnect(request); + } + }, sessionManager); + } + } diff --git a/core/src/main/java/com/glines/socketio/server/SocketIOSession.java b/core/src/main/java/com/glines/socketio/server/SocketIOSession.java index d479368..39aa9db 100644 --- a/core/src/main/java/com/glines/socketio/server/SocketIOSession.java +++ b/core/src/main/java/com/glines/socketio/server/SocketIOSession.java @@ -2,6 +2,8 @@ * The MIT License * Copyright (c) 2010 Tad Glines * + * Contributors: Ovea.com, Mycila.com + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights @@ -22,49 +24,27 @@ */ package com.glines.socketio.server; -import java.io.IOException; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - import com.glines.socketio.common.ConnectionState; import com.glines.socketio.common.DisconnectReason; -import com.glines.socketio.common.SocketIOException; public interface SocketIOSession { - interface Factory { - SocketIOSession createSession(SocketIOInbound inbound); - SocketIOSession getSession(String sessionId); - } + void setAttribute(String key, Object val); + Object getAttribute(String key); - interface SessionTransportHandler extends SocketIOOutbound { - void handle(HttpServletRequest request, HttpServletResponse response, SocketIOSession session) throws IOException; - void sendMessage(SocketIOFrame message) throws SocketIOException; - void disconnectWhenEmpty(); - /** - * Cause connection and all activity to be aborted and all resources to be released. - * The handler is expected to call the session's onShutdown() when it is finished. - * The only session method that the handler can legally call after this is onShutdown(); - */ - void abort(); - } - - interface SessionTask { + interface SessionTask { /** * @return True if task was or was already canceled, false if the task is executing or has executed. */ boolean cancel(); } - - String generateRandomString(int length); - + String getSessionId(); ConnectionState getConnectionState(); SocketIOInbound getInbound(); - SessionTransportHandler getTransportHandler(); + TransportHandler getTransportHandler(); void setHeartbeat(long delay); long getHeartbeat(); @@ -98,7 +78,7 @@ interface SessionTask { /** * @param handler The handler or null if the connection failed. */ - void onConnect(SessionTransportHandler handler); + void onConnect(TransportHandler handler); /** * Pass message through to contained SocketIOInbound diff --git a/core/src/main/java/com/glines/socketio/server/SocketIOSessionManager.java b/core/src/main/java/com/glines/socketio/server/SocketIOSessionManager.java index e36a886..67c3135 100644 --- a/core/src/main/java/com/glines/socketio/server/SocketIOSessionManager.java +++ b/core/src/main/java/com/glines/socketio/server/SocketIOSessionManager.java @@ -2,6 +2,8 @@ * The MIT License * Copyright (c) 2010 Tad Glines * + * Contributors: Ovea.com, Mycila.com + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights @@ -22,342 +24,25 @@ */ package com.glines.socketio.server; -import java.security.SecureRandom; -import java.util.Random; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Executors; -import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicLong; - -import org.eclipse.jetty.util.log.Log; - -import com.glines.socketio.common.ConnectionState; -import com.glines.socketio.common.DisconnectReason; -import com.glines.socketio.common.SocketIOException; - -class SocketIOSessionManager implements SocketIOSession.Factory { - private static final char[] BASE64_ALPHABET = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" - .toCharArray(); - private static final int SESSION_ID_LENGTH = 20; - - private static Random random = new SecureRandom(); - private ConcurrentMap socketIOSessions = new ConcurrentHashMap(); - private ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); - - private static String generateRandomString(int length) { - StringBuilder result = new StringBuilder(length); - byte[] bytes = new byte[length]; - random.nextBytes(bytes); - for (int i = 0; i < bytes.length; i++) { - result.append(BASE64_ALPHABET[bytes[i] & 0x3F]); - } - return result.toString(); - } - - private class SessionImpl implements SocketIOSession { - private final String sessionId; - private SocketIOInbound inbound; - private SessionTransportHandler handler = null; - private ConnectionState state = ConnectionState.CONNECTING; - private long hbDelay = 0; - private SessionTask hbDelayTask = null; - private long timeout = 0; - private SessionTask timeoutTask = null; - private boolean timedout = false; - private AtomicLong messageId = new AtomicLong(0); - private String closeId = null; - - SessionImpl(String sessionId, SocketIOInbound inbound) { - this.sessionId = sessionId; - this.inbound = inbound; - } - - @Override - public String generateRandomString(int length) { - return SocketIOSessionManager.generateRandomString(length); - } - - @Override - public String getSessionId() { - return sessionId; - } - - @Override - public ConnectionState getConnectionState() { - return state; - } - - @Override - public SocketIOInbound getInbound() { - return inbound; - } - - @Override - public SessionTransportHandler getTransportHandler() { - return handler; - } - - private void onTimeout() { - Log.debug("Session["+sessionId+"]: onTimeout"); - if (!timedout) { - timedout = true; - state = ConnectionState.CLOSED; - onDisconnect(DisconnectReason.TIMEOUT); - handler.abort(); - } - } - - @Override - public void startTimeoutTimer() { - clearTimeoutTimer(); - if (!timedout && timeout > 0) { - timeoutTask = scheduleTask(new Runnable() { - @Override - public void run() { - SessionImpl.this.onTimeout(); - } - }, timeout); - } - } - - @Override - public void clearTimeoutTimer() { - if (timeoutTask != null) { - timeoutTask.cancel(); - timeoutTask = null; - } - } - - private void sendPing() { - String data = "" + messageId.incrementAndGet(); - Log.debug("Session["+sessionId+"]: sendPing " + data); - try { - handler.sendMessage(new SocketIOFrame(SocketIOFrame.FrameType.PING, 0, data)); - } catch (SocketIOException e) { - Log.debug("handler.sendMessage failed: ", e); - handler.abort(); - } - startTimeoutTimer(); - } - - @Override - public void startHeartbeatTimer() { - clearHeartbeatTimer(); - if (!timedout && hbDelay > 0) { - hbDelayTask = scheduleTask(new Runnable() { - @Override - public void run() { - sendPing(); - } - }, hbDelay); - } - } - - @Override - public void clearHeartbeatTimer() { - if (hbDelayTask != null) { - hbDelayTask.cancel(); - hbDelayTask = null; - } - } - - @Override - public void setHeartbeat(long delay) { - hbDelay = delay; - } - - @Override - public long getHeartbeat() { - return hbDelay; - } - - @Override - public void setTimeout(long timeout) { - this.timeout = timeout; - } - - @Override - public long getTimeout() { - return timeout; - } - - @Override - public void startClose() { - state = ConnectionState.CLOSING; - closeId = "server"; - try { - handler.sendMessage(new SocketIOFrame(SocketIOFrame.FrameType.CLOSE, 0, closeId)); - } catch (SocketIOException e) { - Log.debug("handler.sendMessage failed: ", e); - handler.abort(); - } - } - - @Override - public void onMessage(SocketIOFrame message) { - switch (message.getFrameType()) { - case SESSION_ID: - case HEARTBEAT_INTERVAL: - // Ignore these two messages types as they are only intended to be from server to client. - break; - case CLOSE: - Log.debug("Session["+sessionId+"]: onClose: " + message.getData()); - onClose(message.getData()); - break; - case PING: - Log.debug("Session["+sessionId+"]: onPing: " + message.getData()); - onPing(message.getData()); - break; - case PONG: - Log.debug("Session["+sessionId+"]: onPong: " + message.getData()); - onPong(message.getData()); - break; - case DATA: - Log.debug("Session["+sessionId+"]: onMessage: " + message.getData()); - onMessage(message.getData()); - break; - default: - // Ignore unknown message types - break; - } - } - - @Override - public void onPing(String data) { - try { - handler.sendMessage(new SocketIOFrame(SocketIOFrame.FrameType.PONG, 0, data)); - } catch (SocketIOException e) { - Log.debug("handler.sendMessage failed: ", e); - handler.abort(); - } - } - - @Override - public void onPong(String data) { - clearTimeoutTimer(); - } - - @Override - public void onClose(String data) { - if (state == ConnectionState.CLOSING) { - if (closeId != null && closeId.equals(data)) { - state = ConnectionState.CLOSED; - onDisconnect(DisconnectReason.CLOSED); - handler.abort(); - } else { - try { - handler.sendMessage(new SocketIOFrame(SocketIOFrame.FrameType.CLOSE, 0, data)); - } catch (SocketIOException e) { - Log.debug("handler.sendMessage failed: ", e); - handler.abort(); - } - } - } else { - state = ConnectionState.CLOSING; - try { - handler.sendMessage(new SocketIOFrame(SocketIOFrame.FrameType.CLOSE, 0, data)); - handler.disconnectWhenEmpty(); - } catch (SocketIOException e) { - Log.debug("handler.sendMessage failed: ", e); - handler.abort(); - } - } - } - - @Override - public SessionTask scheduleTask(Runnable task, long delay) { - final Future future = executor.schedule(task, delay, TimeUnit.MILLISECONDS); - return new SessionTask() { - @Override - public boolean cancel() { - return future.cancel(false); - } - }; - } - - @Override - public void onConnect(SessionTransportHandler handler) { - if (handler == null) { - state = ConnectionState.CLOSED; - inbound = null; - socketIOSessions.remove(sessionId); - } else if (this.handler == null) { - this.handler = handler; - try { - state = ConnectionState.CONNECTED; - inbound.onConnect(handler); - } catch (Throwable e) { - Log.warn("Session["+sessionId+"]: Exception thrown by SocketIOInbound.onConnect()", e); - state = ConnectionState.CLOSED; - handler.abort(); - } - } else { - handler.abort(); - } - } - @Override - public void onMessage(String message) { - if (inbound != null) { - try { - inbound.onMessage(SocketIOFrame.TEXT_MESSAGE_TYPE, message); - } catch (Throwable e) { - Log.warn("Session["+sessionId+"]: Exception thrown by SocketIOInbound.onMessage()", e); - } - } - } +public final class SocketIOSessionManager implements SessionManager { - @Override - public void onDisconnect(DisconnectReason reason) { - Log.debug("Session["+sessionId+"]: onDisconnect: " + reason); - clearTimeoutTimer(); - clearHeartbeatTimer(); - if (inbound != null) { - state = ConnectionState.CLOSED; - try { - inbound.onDisconnect(reason, null); - } catch (Throwable e) { - Log.warn("Session["+sessionId+"]: Exception thrown by SocketIOInbound.onDisconnect()", e); - } - inbound = null; - } - } - - @Override - public void onShutdown() { - Log.debug("Session["+sessionId+"]: onShutdown"); - if (inbound != null) { - if (state == ConnectionState.CLOSING) { - if (closeId != null) { - onDisconnect(DisconnectReason.CLOSE_FAILED); - } else { - onDisconnect(DisconnectReason.CLOSED_REMOTELY); - } - } else { - onDisconnect(DisconnectReason.ERROR); - } - } - socketIOSessions.remove(sessionId); - } - } - - private String generateSessionId() { - return generateRandomString(SESSION_ID_LENGTH); - } + final ConcurrentMap socketIOSessions = new ConcurrentHashMap(); + final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); - @Override - public SocketIOSession createSession(SocketIOInbound inbound) { - SessionImpl impl = new SessionImpl(generateSessionId(), inbound); - socketIOSessions.put(impl.getSessionId(), impl); - return impl; - } + @Override + public SocketIOSession createSession(SocketIOInbound inbound) { + DefaultSession impl = new DefaultSession(this, inbound); + socketIOSessions.put(impl.getSessionId(), impl); + return impl; + } - @Override - public SocketIOSession getSession(String sessionId) { - return socketIOSessions.get(sessionId); - } + @Override + public SocketIOSession getSession(String sessionId) { + return socketIOSessions.get(sessionId); + } } diff --git a/core/src/main/java/com/glines/socketio/server/Transport.java b/core/src/main/java/com/glines/socketio/server/Transport.java index 35a5d98..5d9ea07 100644 --- a/core/src/main/java/com/glines/socketio/server/Transport.java +++ b/core/src/main/java/com/glines/socketio/server/Transport.java @@ -2,6 +2,8 @@ * The MIT License * Copyright (c) 2010 Tad Glines * + * Contributors: Ovea.com, Mycila.com + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights @@ -22,26 +24,30 @@ */ package com.glines.socketio.server; -import java.io.IOException; - import javax.servlet.ServletConfig; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.io.IOException; public interface Transport { - interface InboundFactory { - SocketIOInbound getInbound(HttpServletRequest request); - } - /** - * @return The name of the transport instance. - */ - String getName(); + interface InboundFactory { + SocketIOInbound getInbound(HttpServletRequest request); + } + + /** + * @return The names of the transport instance. + */ + TransportType getType(); + + void setTransportHandlerProvider(TransportHandlerProvider transportHandlerProvider); + + void init(ServletConfig config) throws TransportInitializationException; - void init(ServletConfig config); - - void destroy(); + void destroy(); - void handle(HttpServletRequest request, HttpServletResponse response, - Transport.InboundFactory inboundFactory, SocketIOSession.Factory sessionFactory) throws IOException; + void handle(HttpServletRequest request, + HttpServletResponse response, + Transport.InboundFactory inboundFactory, + SessionManager sessionFactory) throws IOException; } diff --git a/core/src/main/java/com/glines/socketio/server/TransportDiscovery.java b/core/src/main/java/com/glines/socketio/server/TransportDiscovery.java new file mode 100644 index 0000000..0dd13a6 --- /dev/null +++ b/core/src/main/java/com/glines/socketio/server/TransportDiscovery.java @@ -0,0 +1,32 @@ +/** + * The MIT License + * Copyright (c) 2010 Tad Glines + * + * Contributors: Ovea.com, Mycila.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.glines.socketio.server; + +/** + * @author Mathieu Carbou + */ +public interface TransportDiscovery { + Iterable discover(); +} diff --git a/core/src/main/java/com/glines/socketio/server/TransportHandler.java b/core/src/main/java/com/glines/socketio/server/TransportHandler.java new file mode 100644 index 0000000..d582ce0 --- /dev/null +++ b/core/src/main/java/com/glines/socketio/server/TransportHandler.java @@ -0,0 +1,49 @@ +/** + * The MIT License + * Copyright (c) 2010 Tad Glines + * + * Contributors: Ovea.com, Mycila.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.glines.socketio.server; + +import com.glines.socketio.common.SocketIOException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** +* @author Mathieu Carbou +*/ +public interface TransportHandler extends SocketIOOutbound { + void init(SocketIOConfig config); + void setSession(SocketIOSession session); + + void handle(HttpServletRequest request, HttpServletResponse response, SocketIOSession session) throws IOException; + void sendMessage(SocketIOFrame message) throws SocketIOException; + void disconnectWhenEmpty(); + /** + * Cause connection and all activity to be aborted and all resources to be released. + * The handler is expected to call the session's onShutdown() when it is finished. + * The only session method that the handler can legally call after this is onShutdown(); + */ + void abort(); +} diff --git a/core/src/main/java/com/glines/socketio/server/TransportHandlerProvider.java b/core/src/main/java/com/glines/socketio/server/TransportHandlerProvider.java new file mode 100644 index 0000000..dbc0089 --- /dev/null +++ b/core/src/main/java/com/glines/socketio/server/TransportHandlerProvider.java @@ -0,0 +1,40 @@ +/** + * The MIT License + * Copyright (c) 2010 Tad Glines + * + * Contributors: Ovea.com, Mycila.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.glines.socketio.server; + +import java.util.Collection; +import java.util.Map; + +/** + * @author Mathieu Carbou (mathieu.carbou@gmail.com) + */ +public interface TransportHandlerProvider { + TransportHandler get(Class handlerType, TransportType transportType); + boolean isSupported(TransportType type); + + Map> listAll(); + + void init(); +} diff --git a/core/src/main/java/com/glines/socketio/server/TransportInitializationException.java b/core/src/main/java/com/glines/socketio/server/TransportInitializationException.java new file mode 100644 index 0000000..56a545b --- /dev/null +++ b/core/src/main/java/com/glines/socketio/server/TransportInitializationException.java @@ -0,0 +1,45 @@ +/** + * The MIT License + * Copyright (c) 2010 Tad Glines + * + * Contributors: Ovea.com, Mycila.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.glines.socketio.server; + +/** + * @author Mathieu Carbou + */ +public class TransportInitializationException extends RuntimeException { + + private static final long serialVersionUID = -5971560873659475024L; + + public TransportInitializationException(Throwable cause) { + super(cause); + } + + public TransportInitializationException(String message) { + super(message); + } + + public TransportInitializationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/core/src/main/java/com/glines/socketio/server/TransportType.java b/core/src/main/java/com/glines/socketio/server/TransportType.java new file mode 100644 index 0000000..2f5dfb5 --- /dev/null +++ b/core/src/main/java/com/glines/socketio/server/TransportType.java @@ -0,0 +1,58 @@ +/** + * The MIT License + * Copyright (c) 2010 Tad Glines + * + * Contributors: Ovea.com, Mycila.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.glines.socketio.server; + +/** + * @author Mathieu Carbou (mathieu.carbou@gmail.com) + */ +public enum TransportType { + + WEB_SOCKET("websocket"), + FLASH_SOCKET("flashsocket"), + HTML_FILE("htmlfile"), + JSONP_POLLING("jsonp-polling"), + XHR_MULTIPART("xhr-multipart"), + XHR_POLLING("xhr-polling"), + UNKNOWN(""); + + private final String name; + + TransportType(String name) { + this.name = name; + } + + @Override + public String toString() { + return name; + } + + public static TransportType from(String name) { + for (TransportType type : values()) { + if (type.name.equals(name)) + return type; + } + return UNKNOWN; + } +} diff --git a/core/src/main/java/com/glines/socketio/server/transport/AbstractDataHandler.java b/core/src/main/java/com/glines/socketio/server/transport/AbstractDataHandler.java new file mode 100644 index 0000000..e7d5bcb --- /dev/null +++ b/core/src/main/java/com/glines/socketio/server/transport/AbstractDataHandler.java @@ -0,0 +1,49 @@ +/** + * The MIT License + * Copyright (c) 2010 Tad Glines + * + * Contributors: Ovea.com, Mycila.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.glines.socketio.server.transport; + +import com.glines.socketio.server.SocketIOConfig; + +/** + * @author Mathieu Carbou + */ +abstract class AbstractDataHandler implements DataHandler { + + private SocketIOConfig config; + + @Override + public final void init(SocketIOConfig config) { + this.config = config; + init(); + } + + protected final SocketIOConfig getConfig() { + return config; + } + + protected void init() { + } + +} diff --git a/core/src/main/java/com/glines/socketio/server/transport/AbstractHttpTransport.java b/core/src/main/java/com/glines/socketio/server/transport/AbstractHttpTransport.java index 631e15b..5b5be5e 100644 --- a/core/src/main/java/com/glines/socketio/server/transport/AbstractHttpTransport.java +++ b/core/src/main/java/com/glines/socketio/server/transport/AbstractHttpTransport.java @@ -2,6 +2,8 @@ * The MIT License * Copyright (c) 2010 Tad Glines * + * Contributors: Ovea.com, Mycila.com + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights @@ -22,87 +24,85 @@ */ package com.glines.socketio.server.transport; -import java.io.IOException; +import com.glines.socketio.server.*; +import com.glines.socketio.util.Web; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; - -import com.glines.socketio.server.SocketIOSession; -import com.glines.socketio.server.SocketIOSession.SessionTransportHandler; -import com.glines.socketio.server.Transport; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; public abstract class AbstractHttpTransport extends AbstractTransport { - /** - * This is a sane default based on the timeout values of various browsers. - */ - public static final long HTTP_REQUEST_TIMEOUT = 30*1000; - - /** - * The amount of time the session will wait before trying to send a ping. - * Using a value of half the HTTP_REQUEST_TIMEOUT should be good enough. - */ - public static final long HEARTBEAT_DELAY = HTTP_REQUEST_TIMEOUT/2; - - /** - * This specifies how long to wait for a pong (ping response). - */ - public static final long HEARTBEAT_TIMEOUT = 10*1000; - /** - * For non persistent connection transports, this is the amount of time to wait - * for messages before returning empty results. - */ - public static long REQUEST_TIMEOUT = 20*1000; - - protected static final String SESSION_KEY = "com.glines.socketio.server.AbstractHttpTransport.Session"; + private static final Logger LOGGER = Logger.getLogger(AbstractHttpTransport.class.getName()); + public static final String SESSION_KEY = AbstractHttpTransport.class.getName() + ".Session"; + + @Override + public final void handle(HttpServletRequest request, + HttpServletResponse response, + Transport.InboundFactory inboundFactory, + SessionManager sessionFactory) throws IOException { - public AbstractHttpTransport() { - } + if (LOGGER.isLoggable(Level.FINE)) + LOGGER.fine("Handling request " + request.getRequestURI() + " by " + getClass().getName()); - protected abstract SocketIOSession connect( - HttpServletRequest request, - HttpServletResponse response, - Transport.InboundFactory inboundFactory, - SocketIOSession.Factory sessionFactory) throws IOException; + Object obj = request.getAttribute(SESSION_KEY); + SocketIOSession session = null; + String sessionId = null; + if (obj != null) { + session = (SocketIOSession) obj; + } else { + sessionId = Web.extractSessionId(request); + if (sessionId != null && sessionId.length() > 0) { + session = sessionFactory.getSession(sessionId); + } + } + if (session != null) { + TransportHandler handler = session.getTransportHandler(); + if (handler != null) { + handler.handle(request, response, session); + } else { + session.onShutdown(); + response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } + } else if (sessionId != null && sessionId.length() > 0) { + if (LOGGER.isLoggable(Level.FINE)) + LOGGER.fine("got session id " + sessionId + " but no session found"); + response.sendError(HttpServletResponse.SC_BAD_REQUEST); + } else { + if ("GET".equals(request.getMethod())) { + session = connect(request, response, inboundFactory, sessionFactory); + if (session == null) { + response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE); + } + } else { + response.sendError(HttpServletResponse.SC_BAD_REQUEST); + } + } + } - @Override - public void handle(HttpServletRequest request, - HttpServletResponse response, - Transport.InboundFactory inboundFactory, - SocketIOSession.Factory sessionFactory) - throws IOException { + private SocketIOSession connect(HttpServletRequest request, + HttpServletResponse response, + InboundFactory inboundFactory, + SessionManager sessionFactory) throws IOException { + SocketIOInbound inbound = inboundFactory.getInbound(request); + if (inbound != null) { + SocketIOSession session = sessionFactory.createSession(inbound); + // get and init data handler + DataHandler dataHandler = newDataHandler(session); + dataHandler.init(getConfig()); + // get and init transport handler + TransportHandler transportHandler = newHandler(ConnectableTransportHandler.class, session); + ConnectableTransportHandler connectableTransportHandler = ConnectableTransportHandler.class.cast(transportHandler); + connectableTransportHandler.setDataHandler(dataHandler); + transportHandler.init(getConfig()); + // connect transport to session + connectableTransportHandler.connect(request, response); + return session; + } + return null; + } - Object obj = request.getAttribute(SESSION_KEY); - SocketIOSession session = null; - String sessionId = null; - if (obj != null) { - session = (SocketIOSession)obj; - } else { - sessionId = extractSessionId(request); - if (sessionId != null && sessionId.length() > 0) { - session = sessionFactory.getSession(sessionId); - } - } - - if (session != null) { - SessionTransportHandler handler = session.getTransportHandler(); - if (handler != null) { - handler.handle(request, response, session); - } else { - session.onShutdown(); - response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - } - } else if (sessionId != null && sessionId.length() > 0) { - response.sendError(HttpServletResponse.SC_BAD_REQUEST); - } else { - if ("GET".equals(request.getMethod())) { - session = connect(request, response, inboundFactory, sessionFactory); - if (session == null) { - response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE); - } - } else { - response.sendError(HttpServletResponse.SC_BAD_REQUEST); - } - } - } + protected abstract DataHandler newDataHandler(SocketIOSession session); } diff --git a/core/src/main/java/com/glines/socketio/server/transport/ConnectionTimeoutPreventor.java b/core/src/main/java/com/glines/socketio/server/transport/ConnectionTimeoutPreventor.java deleted file mode 100644 index 6118e35..0000000 --- a/core/src/main/java/com/glines/socketio/server/transport/ConnectionTimeoutPreventor.java +++ /dev/null @@ -1,72 +0,0 @@ -/** - * The MIT License - * Copyright (c) 2010 Tad Glines - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.glines.socketio.server.transport; - -import org.eclipse.jetty.io.AsyncEndPoint; -import org.eclipse.jetty.io.EndPoint; -import org.eclipse.jetty.io.nio.SelectChannelEndPoint; -import org.eclipse.jetty.server.HttpConnection; - -/** - * Jetty will close a connection even if there is continuous outbound data if the request's response - * is not completed within maxIdleTime milliseconds. This is not appropriate for a persistent connection - * SocketIO transport. IN order to prevent this, without disabling the maxIdleTime completely, - * this class is used to obtain a @{link IdleCheck} instance that can be used to reset the idle timeout. - */ -public class ConnectionTimeoutPreventor { - interface IdleCheck { - void activity(); - } - - /** - * This must be called within the context of an active HTTP request. - */ - public static IdleCheck newTimeoutPreventor() { - HttpConnection httpConnection = HttpConnection.getCurrentConnection(); - if (httpConnection != null) { - EndPoint endPoint = httpConnection.getEndPoint(); - if (endPoint instanceof AsyncEndPoint) { - ((AsyncEndPoint)endPoint).cancelIdle(); - } - if (endPoint instanceof SelectChannelEndPoint) { - final SelectChannelEndPoint scep = (SelectChannelEndPoint)endPoint; - scep.cancelIdle(); - return new IdleCheck() { - @Override - public void activity() { - scep.scheduleIdle(); - } - }; - } else { - return new IdleCheck() { - @Override - public void activity() { - // Do nothing - } - }; - } - } else { - return null; - } - } -} diff --git a/core/src/main/java/com/glines/socketio/server/transport/AbstractTransport.java b/core/src/main/java/com/glines/socketio/server/transport/DataHandler.java similarity index 64% rename from core/src/main/java/com/glines/socketio/server/transport/AbstractTransport.java rename to core/src/main/java/com/glines/socketio/server/transport/DataHandler.java index 2ade94e..f6a4e80 100644 --- a/core/src/main/java/com/glines/socketio/server/transport/AbstractTransport.java +++ b/core/src/main/java/com/glines/socketio/server/transport/DataHandler.java @@ -2,6 +2,8 @@ * The MIT License * Copyright (c) 2010 Tad Glines * + * Contributors: Ovea.com, Mycila.com + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights @@ -22,31 +24,28 @@ */ package com.glines.socketio.server.transport; -import javax.servlet.ServletConfig; +import com.glines.socketio.server.SocketIOConfig; + +import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * @author Mathieu Carbou + */ +public interface DataHandler { + + void init(SocketIOConfig config); + + boolean isConnectionPersistent(); + + void onStartSend(HttpServletResponse response) throws IOException; + + void onWriteData(ServletResponse response, String data) throws IOException; + + void onFinishSend(ServletResponse response) throws IOException; + + void onConnect(HttpServletRequest request, HttpServletResponse response) throws IOException; -import com.glines.socketio.server.Transport; - -public abstract class AbstractTransport implements Transport { - protected String extractSessionId(HttpServletRequest request) { - String path = request.getPathInfo(); - if (path != null && path.length() > 0 && !"/".equals(path)) { - if (path.startsWith("/")) path = path.substring(1); - String[] parts = path.split("/"); - if (parts.length >= 2) { - return parts[1] == null ? null : (parts[1].length() == 0 ? null : parts[1]); - } - } - return null; - } - - @Override - public void init(ServletConfig config) { - - } - - @Override - public void destroy() { - - } } diff --git a/core/src/main/java/com/glines/socketio/server/transport/FlashSocketTransport.java b/core/src/main/java/com/glines/socketio/server/transport/FlashSocketTransport.java index f0352fe..f4b69ce 100644 --- a/core/src/main/java/com/glines/socketio/server/transport/FlashSocketTransport.java +++ b/core/src/main/java/com/glines/socketio/server/transport/FlashSocketTransport.java @@ -2,6 +2,8 @@ * The MIT License * Copyright (c) 2010 Tad Glines * + * Contributors: Ovea.com, Mycila.com + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights @@ -22,11 +24,12 @@ */ package com.glines.socketio.server.transport; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.PrintWriter; +import com.glines.socketio.server.*; +import com.glines.socketio.util.IO; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.*; import java.net.InetSocketAddress; import java.net.Socket; import java.nio.channels.ClosedChannelException; @@ -36,178 +39,181 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; - -import javax.servlet.ServletConfig; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.eclipse.jetty.util.IO; - -import com.glines.socketio.server.SocketIOSession; -import com.glines.socketio.server.Transport; - -public class FlashSocketTransport extends WebSocketTransport { - public static final String TRANSPORT_NAME = "flashsocket"; - public static final String FLASHPOLICY_SERVER_HOST_KEY = "flashPolicyServerHost"; - public static final String FLASHPOLICY_SERVER_PORT_KEY = "flashPolicyServerPort"; - public static final String FLASHPOLICY_DOMAIN_KEY = "flashPolicyDomain"; - public static final String FLASHPOLICY_PORTS_KEY = "flashPolicyPorts"; - - private static final String FLASHFILE_NAME = "WebSocketMain.swf"; - private static final String FLASHFILE_PATH = TRANSPORT_NAME + "/" + FLASHFILE_NAME; - private ServerSocketChannel flashPolicyServer = null; - private ExecutorService executor = Executors.newCachedThreadPool(); - private Future policyAcceptorThread = null; - private String flashPolicyServerHost = null; - private short flashPolicyServerPort = 843; - private String flashPolicyDomain = null; - private String flashPolicyPorts = null; - - - public FlashSocketTransport(int bufferSize, int maxIdleTime) { - super(bufferSize, maxIdleTime); - } - - @Override - public String getName() { - return TRANSPORT_NAME; - } - - @Override - public void init(ServletConfig config) { - flashPolicyServerHost = config.getInitParameter(FLASHPOLICY_SERVER_HOST_KEY); - flashPolicyDomain = config.getInitParameter(FLASHPOLICY_DOMAIN_KEY); - flashPolicyPorts = config.getInitParameter(FLASHPOLICY_PORTS_KEY); - String port = config.getInitParameter(FLASHPOLICY_SERVER_PORT_KEY); - if (port != null) { - flashPolicyServerPort = Short.parseShort(port); - } - if (flashPolicyServerHost != null && flashPolicyDomain != null && flashPolicyPorts != null) { - try { - startFlashPolicyServer(); - } catch (IOException e) { - // Ignore - } - } - } - - @Override - public void destroy() { - stopFlashPolicyServer(); - } - - @Override - public void handle(HttpServletRequest request, - HttpServletResponse response, - Transport.InboundFactory inboundFactory, - SocketIOSession.Factory sessionFactory) - throws IOException { - - String path = request.getPathInfo(); - if (path == null || path.length() == 0 || "/".equals(path)) { - response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid " + TRANSPORT_NAME + " transport request"); - return; - } - if (path.startsWith("/")) path = path.substring(1); - String[] parts = path.split("/"); - - if ("GET".equals(request.getMethod()) && TRANSPORT_NAME.equals(parts[0])) { - if (!FLASHFILE_PATH.equals(path)) { - super.handle(request, response, inboundFactory, sessionFactory); - } else { - response.setContentType("application/x-shockwave-flash"); - InputStream is = this.getClass().getClassLoader().getResourceAsStream("com/glines/socketio/" + FLASHFILE_NAME); - OutputStream os = response.getOutputStream(); - try { - IO.copy(is, os); - } catch (IOException e) { - // TODO: Do we care? - } - } - } else { - response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid " + TRANSPORT_NAME + " transport request"); - } - } - - /** - * Starts this server, binding to the previously passed SocketAddress. - */ - public void startFlashPolicyServer() throws IOException { - final String POLICY_FILE_REQUEST = ""; - flashPolicyServer = ServerSocketChannel.open(); - flashPolicyServer.socket().setReuseAddress(true); - flashPolicyServer.socket().bind(new InetSocketAddress(flashPolicyServerHost, flashPolicyServerPort)); - flashPolicyServer.configureBlocking(true); - - // Spawn a new server acceptor thread, which must accept incoming - // connections indefinitely - until a ClosedChannelException is thrown. - policyAcceptorThread = executor.submit(new Runnable() { - @Override - public void run() { - try { - while (true) { - final SocketChannel serverSocket = flashPolicyServer.accept(); - executor.submit(new Runnable() { - @Override - public void run() { - try { - serverSocket.configureBlocking(true); - Socket s = serverSocket.socket(); - StringBuilder request = new StringBuilder(); - InputStreamReader in = new InputStreamReader(s.getInputStream()); - int c; - while ((c = in.read()) != 0 && request.length() <= POLICY_FILE_REQUEST.length()) { - request.append((char)c); - } - if (request.toString().equalsIgnoreCase(POLICY_FILE_REQUEST) || - flashPolicyDomain != null && flashPolicyPorts != null) { - PrintWriter out = new PrintWriter(s.getOutputStream()); - out.println(""); - out.write(0); - out.flush(); - } - serverSocket.close(); - } catch (IOException e) { - // TODO: Add loging - } finally { - try { - serverSocket.close(); - } catch (IOException e) { - // Ignore error on close. - } - } - } - }); - } - } catch (ClosedChannelException e) { - return; - } catch (IOException e) { - throw new IllegalStateException("Server should not throw a misunderstood IOException", e); - } - } - }); - } - - private void stopFlashPolicyServer() { - if (flashPolicyServer != null) { - try { - flashPolicyServer.close(); - } catch (IOException e) { - // Ignore - } - } - if (policyAcceptorThread != null) { - try { - policyAcceptorThread.get(); - } catch (InterruptedException e) { - throw new IllegalStateException(); - } catch (ExecutionException e) { - throw new IllegalStateException("Server thread threw an exception", e.getCause()); - } - if (!policyAcceptorThread.isDone()) { - throw new IllegalStateException("Server acceptor thread has not stopped."); - } - } - } +import java.util.logging.Level; +import java.util.logging.Logger; + +public class FlashSocketTransport extends AbstractTransport { + + public static final String PARAM_FLASHPOLICY_DOMAIN = "flashPolicyDomain"; + public static final String PARAM_FLASHPOLICY_SERVER_HOST = "flashPolicyServerHost"; + public static final String PARAM_FLASHPOLICY_SERVER_PORT = "flashPolicyServerPort"; + public static final String PARAM_FLASHPOLICY_PORTS = "flashPolicyPorts"; + + private static final Logger LOGGER = Logger.getLogger(FlashSocketTransport.class.getName()); + private static final String FLASHFILE_NAME = "WebSocketMain.swf"; + private static final String FLASHFILE_PATH = TransportType.FLASH_SOCKET + "/" + FLASHFILE_NAME; + + private ServerSocketChannel flashPolicyServer; + private ExecutorService executor = Executors.newCachedThreadPool(); + private Future policyAcceptorThread; + + private int flashPolicyServerPort; + private String flashPolicyServerHost; + private String flashPolicyDomain; + private String flashPolicyPorts; + + private Transport delegate; + + @Override + public TransportType getType() { + return TransportType.FLASH_SOCKET; + } + + @Override + public void init() throws TransportInitializationException { + this.flashPolicyDomain = getConfig().getString(PARAM_FLASHPOLICY_DOMAIN); + this.flashPolicyPorts = getConfig().getString(PARAM_FLASHPOLICY_PORTS); + this.flashPolicyServerHost = getConfig().getString(PARAM_FLASHPOLICY_SERVER_HOST); + this.flashPolicyServerPort = getConfig().getInt(PARAM_FLASHPOLICY_SERVER_PORT, 843); + this.delegate = getConfig().getWebSocketTransport(); + + if (LOGGER.isLoggable(Level.FINE)) + LOGGER.fine(getType() + " configuration:\n" + + " - flashPolicyDomain=" + flashPolicyDomain + "\n" + + " - flashPolicyPorts=" + flashPolicyPorts + "\n" + + " - flashPolicyServerHost=" + flashPolicyServerHost + "\n" + + " - flashPolicyServerPort=" + flashPolicyServerPort + "\n" + + " - websocket delegate=" + (delegate == null ? "" : delegate.getClass().getName())); + + if (delegate == null) + throw new TransportInitializationException("No WebSocket transport available for this transport: " + getClass().getName()); + + if (flashPolicyServerHost != null && flashPolicyDomain != null && flashPolicyPorts != null) { + try { + startFlashPolicyServer(); + } catch (IOException e) { + // Ignore + } + } + } + + @Override + public void destroy() { + stopFlashPolicyServer(); + } + + @Override + public void handle(HttpServletRequest request, + HttpServletResponse response, + Transport.InboundFactory inboundFactory, + SessionManager sessionFactory) throws IOException { + + String path = request.getPathInfo(); + if (path == null || path.length() == 0 || "/".equals(path)) { + response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid " + TransportType.FLASH_SOCKET + " transport request"); + return; + } + if (path.startsWith("/")) path = path.substring(1); + String[] parts = path.split("/"); + if ("GET".equals(request.getMethod()) && TransportType.FLASH_SOCKET.name().equals(parts[0])) { + if (!FLASHFILE_PATH.equals(path)) { + delegate.handle(request, response, inboundFactory, sessionFactory); + } else { + response.setContentType("application/x-shockwave-flash"); + InputStream is = this.getClass().getClassLoader().getResourceAsStream("com/glines/socketio/" + FLASHFILE_NAME); + OutputStream os = response.getOutputStream(); + try { + IO.copy(is, os); + } catch (IOException e) { + LOGGER.log(Level.FINE, "Error writing " + FLASHFILE_NAME + ": " + e.getMessage(), e); + } + } + } else { + response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid " + TransportType.FLASH_SOCKET + " transport request"); + } + } + + /** + * Starts this server, binding to the previously passed SocketAddress. + */ + public void startFlashPolicyServer() throws IOException { + final String POLICY_FILE_REQUEST = ""; + flashPolicyServer = ServerSocketChannel.open(); + flashPolicyServer.socket().setReuseAddress(true); + flashPolicyServer.socket().bind(new InetSocketAddress(flashPolicyServerHost, flashPolicyServerPort)); + flashPolicyServer.configureBlocking(true); + + // Spawn a new server acceptor thread, which must accept incoming + // connections indefinitely - until a ClosedChannelException is thrown. + policyAcceptorThread = executor.submit(new Runnable() { + @Override + public void run() { + try { + while (!Thread.currentThread().isInterrupted()) { + final SocketChannel serverSocket = flashPolicyServer.accept(); + executor.submit(new Runnable() { + @Override + public void run() { + try { + serverSocket.configureBlocking(true); + Socket s = serverSocket.socket(); + StringBuilder request = new StringBuilder(); + InputStreamReader in = new InputStreamReader(s.getInputStream()); + int c; + while ((c = in.read()) != 0 && request.length() <= POLICY_FILE_REQUEST.length()) { + request.append((char) c); + } + if (request.toString().equalsIgnoreCase(POLICY_FILE_REQUEST) || + flashPolicyDomain != null && flashPolicyPorts != null) { + PrintWriter out = new PrintWriter(s.getOutputStream()); + out.println(""); + out.write(0); + out.flush(); + } + serverSocket.close(); + } catch (IOException e) { + LOGGER.log(Level.FINE, "startFlashPolicyServer: " + e.getMessage(), e); + } finally { + try { + serverSocket.close(); + } catch (IOException e) { + // Ignore error on close. + } + } + } + }); + } + } catch (ClosedChannelException e) { + Thread.currentThread().interrupt(); + } catch (IOException e) { + Thread.currentThread().interrupt(); + throw new IllegalStateException("Server should not throw a misunderstood IOException", e); + } + } + }); + } + + private void stopFlashPolicyServer() { + if (flashPolicyServer != null) { + try { + flashPolicyServer.close(); + } catch (IOException e) { + // Ignore + } + } + if (policyAcceptorThread != null) { + try { + policyAcceptorThread.get(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IllegalStateException(); + } catch (ExecutionException e) { + throw new IllegalStateException("Server thread threw an exception", e.getCause()); + } + if (!policyAcceptorThread.isDone()) { + throw new IllegalStateException("Server acceptor thread has not stopped."); + } + } + } } diff --git a/core/src/main/java/com/glines/socketio/server/transport/HTMLFileDataHandler.java b/core/src/main/java/com/glines/socketio/server/transport/HTMLFileDataHandler.java new file mode 100644 index 0000000..c4d3b0a --- /dev/null +++ b/core/src/main/java/com/glines/socketio/server/transport/HTMLFileDataHandler.java @@ -0,0 +1,97 @@ +/** + * The MIT License + * Copyright (c) 2010 Tad Glines + * + * Contributors: Ovea.com, Mycila.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.glines.socketio.server.transport; + +import com.glines.socketio.server.SocketIOFrame; +import com.glines.socketio.server.SocketIOSession; +import com.glines.socketio.util.JSON; + +import javax.servlet.ServletOutputStream; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Arrays; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Mathieu Carbou + */ +final class HTMLFileDataHandler extends AbstractDataHandler { + + private static final Logger LOGGER = Logger.getLogger(HTMLFileDataHandler.class.getName()); + private static final long DEFAULT_HEARTBEAT_DELAY = 15 * 1000; + + private final SocketIOSession session; + + private long hearbeat; + + HTMLFileDataHandler(SocketIOSession session) { + this.session = session; + } + + @Override + protected void init() { + this.hearbeat = getConfig().getHeartbeatDelay(DEFAULT_HEARTBEAT_DELAY); + if (LOGGER.isLoggable(Level.FINE)) + LOGGER.fine(getConfig().getNamespace() + " data handler configuration:\n" + + " - heartbeatDelay=" + hearbeat); + } + + @Override + public boolean isConnectionPersistent() { + return true; + } + + @Override + public void onStartSend(HttpServletResponse response) throws IOException { + response.setContentType("text/html"); + response.setHeader("Connection", "keep-alive"); + response.setHeader("Transfer-Encoding", "chunked"); + char[] spaces = new char[244]; + Arrays.fill(spaces, ' '); + ServletOutputStream os = response.getOutputStream(); + os.print("" + new String(spaces)); + response.flushBuffer(); + } + + @Override + public void onWriteData(ServletResponse response, String data) throws IOException { + response.getOutputStream().print(""); + response.flushBuffer(); + } + + @Override + public void onFinishSend(ServletResponse response) throws IOException { + } + + @Override + public void onConnect(HttpServletRequest request, HttpServletResponse response) throws IOException { + onStartSend(response); + onWriteData(response, SocketIOFrame.encode(SocketIOFrame.FrameType.SESSION_ID, 0, session.getSessionId())); + onWriteData(response, SocketIOFrame.encode(SocketIOFrame.FrameType.HEARTBEAT_INTERVAL, 0, "" + hearbeat)); + } +} diff --git a/core/src/main/java/com/glines/socketio/server/transport/HTMLFileTransport.java b/core/src/main/java/com/glines/socketio/server/transport/HTMLFileTransport.java index 5209374..dd0b4c7 100644 --- a/core/src/main/java/com/glines/socketio/server/transport/HTMLFileTransport.java +++ b/core/src/main/java/com/glines/socketio/server/transport/HTMLFileTransport.java @@ -2,6 +2,8 @@ * The MIT License * Copyright (c) 2010 Tad Glines * + * Contributors: Ovea.com, Mycila.com + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights @@ -22,69 +24,17 @@ */ package com.glines.socketio.server.transport; -import java.io.IOException; -import java.util.Arrays; - -import javax.servlet.ServletOutputStream; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.eclipse.jetty.util.ajax.JSON; - -import com.glines.socketio.server.SocketIOFrame; import com.glines.socketio.server.SocketIOSession; -import com.glines.socketio.server.transport.ConnectionTimeoutPreventor.IdleCheck; - -public class HTMLFileTransport extends XHRTransport { - public static final String TRANSPORT_NAME = "htmlfile"; - - private class HTMLFileSessionHelper extends XHRSessionHelper { - private final IdleCheck idleCheck; - - HTMLFileSessionHelper(SocketIOSession session, IdleCheck idleCheck) { - super(session, true); - this.idleCheck = idleCheck; - } - - protected void startSend(HttpServletResponse response) throws IOException { - response.setContentType("text/html"); - response.setHeader("Connection", "keep-alive"); - response.setHeader("Transfer-Encoding", "chunked"); - char[] spaces = new char[244]; - Arrays.fill(spaces, ' '); - ServletOutputStream os = response.getOutputStream(); - os.print("" + new String(spaces)); - response.flushBuffer(); - } - - protected void writeData(ServletResponse response, String data) throws IOException { - idleCheck.activity(); - response.getOutputStream().print(""); - response.flushBuffer(); - } - - protected void finishSend(ServletResponse response) throws IOException {}; - - protected void customConnect(HttpServletRequest request, - HttpServletResponse response) throws IOException { - startSend(response); - writeData(response, SocketIOFrame.encode(SocketIOFrame.FrameType.SESSION_ID, 0, session.getSessionId())); - writeData(response, SocketIOFrame.encode(SocketIOFrame.FrameType.HEARTBEAT_INTERVAL, 0, "" + HEARTBEAT_DELAY)); - } - } - - public HTMLFileTransport(int bufferSize, int maxIdleTime) { - super(bufferSize, maxIdleTime); - } - - @Override - public String getName() { - return TRANSPORT_NAME; - } - - protected XHRSessionHelper createHelper(SocketIOSession session) { - IdleCheck idleCheck = ConnectionTimeoutPreventor.newTimeoutPreventor(); - return new HTMLFileSessionHelper(session, idleCheck); - } +import com.glines.socketio.server.TransportType; + +public class HTMLFileTransport extends AbstractHttpTransport { + @Override + public TransportType getType() { + return TransportType.HTML_FILE; + } + + @Override + protected DataHandler newDataHandler(SocketIOSession session) { + return new HTMLFileDataHandler(session); + } } diff --git a/core/src/main/java/com/glines/socketio/server/transport/JSONPPollingDataHandler.java b/core/src/main/java/com/glines/socketio/server/transport/JSONPPollingDataHandler.java new file mode 100644 index 0000000..06b6477 --- /dev/null +++ b/core/src/main/java/com/glines/socketio/server/transport/JSONPPollingDataHandler.java @@ -0,0 +1,99 @@ +/** + * The MIT License + * Copyright (c) 2010 Tad Glines + * + * Contributors: Ovea.com, Mycila.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.glines.socketio.server.transport; + +import com.glines.socketio.server.SocketIOFrame; +import com.glines.socketio.server.SocketIOSession; + +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Mathieu Carbou + */ +final class JSONPPollingDataHandler extends AbstractDataHandler { + + /** + * For non persistent connection transports, this is the amount of time to wait + * for messages before returning empty results. + */ + private static final long DEFAULT_TIMEOUT = 20 * 1000; + private static final String FRAME_ID = JSONPPollingDataHandler.class.getName() + "FRAME_ID"; + private static final Logger LOGGER = Logger.getLogger(JSONPPollingDataHandler.class.getName()); + + private final SocketIOSession session; + private long timeout; + + JSONPPollingDataHandler(SocketIOSession session) { + this.session = session; + } + + @Override + protected void init() { + this.timeout = getConfig().getTimeout(DEFAULT_TIMEOUT); + if (LOGGER.isLoggable(Level.FINE)) + LOGGER.fine(getConfig().getNamespace() + " data handler configuration:\n" + + " - timeout=" + timeout); + } + + @Override + public boolean isConnectionPersistent() { + return false; + } + + @Override + public void onStartSend(HttpServletResponse response) throws IOException { + response.setContentType("text/javascript; charset=UTF-8"); + response.getOutputStream().print("io.JSONP[" + session.getAttribute(FRAME_ID) + "]._('"); + } + + @Override + public void onWriteData(ServletResponse response, String data) throws IOException { + response.getOutputStream().print(data); + } + + @Override + public void onFinishSend(ServletResponse response) throws IOException { + response.getOutputStream().print("');"); + response.flushBuffer(); + } + + @Override + public void onConnect(HttpServletRequest request, HttpServletResponse response) throws IOException { + String path = request.getPathInfo(); + if (path.startsWith("/")) path = path.substring(1); + String[] parts = path.split("/"); + if (parts.length >= 4) { + session.setAttribute(FRAME_ID, Integer.parseInt(parts[3])); + } + onStartSend(response); + onWriteData(response, SocketIOFrame.encode(SocketIOFrame.FrameType.SESSION_ID, 0, session.getSessionId())); + onWriteData(response, SocketIOFrame.encode(SocketIOFrame.FrameType.HEARTBEAT_INTERVAL, 0, "" + timeout)); + } +} diff --git a/core/src/main/java/com/glines/socketio/server/transport/JSONPPollingTransport.java b/core/src/main/java/com/glines/socketio/server/transport/JSONPPollingTransport.java index 4af5591..47dedeb 100644 --- a/core/src/main/java/com/glines/socketio/server/transport/JSONPPollingTransport.java +++ b/core/src/main/java/com/glines/socketio/server/transport/JSONPPollingTransport.java @@ -2,6 +2,8 @@ * The MIT License * Copyright (c) 2010 Tad Glines * + * Contributors: Ovea.com, Mycila.com + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights @@ -22,65 +24,17 @@ */ package com.glines.socketio.server.transport; -import java.io.IOException; - -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import com.glines.socketio.server.SocketIOFrame; import com.glines.socketio.server.SocketIOSession; - -public class JSONPPollingTransport extends XHRTransport { - public static final String TRANSPORT_NAME = "jsonp-polling"; - private long jsonpIndex = -1; - - protected class XHRPollingSessionHelper extends XHRSessionHelper { - - XHRPollingSessionHelper(SocketIOSession session) { - super(session, false); - } - - protected void startSend(HttpServletResponse response) throws IOException { - response.setContentType("text/javascript; charset=UTF-8"); - response.getOutputStream().print("io.JSONP["+ jsonpIndex +"]._('"); - } - - @Override - protected void writeData(ServletResponse response, String data) throws IOException { - response.getOutputStream().print(data); - } - - protected void finishSend(ServletResponse response) throws IOException { - response.getOutputStream().print("');"); - response.flushBuffer(); - } - - protected void customConnect(HttpServletRequest request, - HttpServletResponse response) throws IOException { - String path = request.getPathInfo(); - if (path.startsWith("/")) path = path.substring(1); - String[] parts = path.split("/"); - if (parts.length >= 4) { - jsonpIndex = Integer.parseInt(parts[3]); - } - startSend(response); - writeData(response, SocketIOFrame.encode(SocketIOFrame.FrameType.SESSION_ID, 0, session.getSessionId())); - writeData(response, SocketIOFrame.encode(SocketIOFrame.FrameType.HEARTBEAT_INTERVAL, 0, "" + REQUEST_TIMEOUT)); - } - } - - public JSONPPollingTransport(int bufferSize, int maxIdleTime) { - super(bufferSize, maxIdleTime); - } - - @Override - public String getName() { - return TRANSPORT_NAME; - } - - - protected XHRPollingSessionHelper createHelper(SocketIOSession session) { - return new XHRPollingSessionHelper(session); - } +import com.glines.socketio.server.TransportType; + +public class JSONPPollingTransport extends AbstractHttpTransport { + @Override + public TransportType getType() { + return TransportType.JSONP_POLLING; + } + + @Override + protected DataHandler newDataHandler(SocketIOSession session) { + return new JSONPPollingDataHandler(session); + } } diff --git a/core/src/main/java/com/glines/socketio/server/transport/WebSocketTransport.java b/core/src/main/java/com/glines/socketio/server/transport/WebSocketTransport.java deleted file mode 100644 index badc9f7..0000000 --- a/core/src/main/java/com/glines/socketio/server/transport/WebSocketTransport.java +++ /dev/null @@ -1,259 +0,0 @@ -/** - * The MIT License - * Copyright (c) 2010 Tad Glines - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.glines.socketio.server.transport; - -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.util.List; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.eclipse.jetty.util.log.Log; -import org.eclipse.jetty.websocket.WebSocket; -import org.eclipse.jetty.websocket.WebSocketFactory; - -import com.glines.socketio.common.ConnectionState; -import com.glines.socketio.common.DisconnectReason; -import com.glines.socketio.common.SocketIOException; -import com.glines.socketio.server.SocketIOClosedException; -import com.glines.socketio.server.SocketIOInbound; -import com.glines.socketio.server.SocketIOFrame; -import com.glines.socketio.server.SocketIOSession; -import com.glines.socketio.server.Transport; - -public class WebSocketTransport extends AbstractTransport { - public static final String TRANSPORT_NAME = "websocket"; - public static final long CONNECTION_TIMEOUT = 10*1000; - private final WebSocketFactory wsFactory; - private final long maxIdleTime; - - private class SessionWrapper implements WebSocket, SocketIOSession.SessionTransportHandler { - private final SocketIOSession session; - private Outbound outbound = null; - private boolean initiated = false; - - SessionWrapper(SocketIOSession session) { - this.session = session; - session.setHeartbeat(maxIdleTime/2); - session.setTimeout(CONNECTION_TIMEOUT); - } - - /* - * (non-Javadoc) - * @see org.eclipse.jetty.websocket.WebSocket#onConnect(org.eclipse.jetty.websocket.WebSocket.Outbound) - */ - @Override - public void onConnect(final Outbound outbound) { - this.outbound = outbound; - } - - /* - * (non-Javadoc) - * @see org.eclipse.jetty.websocket.WebSocket#onDisconnect() - */ - @Override - public void onDisconnect() { - session.onShutdown(); - } - - /* - * (non-Javadoc) - * @see org.eclipse.jetty.websocket.WebSocket#onMessage(byte, java.lang.String) - */ - @Override - public void onMessage(byte frame, String message) { - session.startHeartbeatTimer(); - if (!initiated) { - if ("OPEN".equals(message)) { - try { - outbound.sendMessage(SocketIOFrame.encode(SocketIOFrame.FrameType.SESSION_ID, 0, session.getSessionId())); - outbound.sendMessage(SocketIOFrame.encode(SocketIOFrame.FrameType.HEARTBEAT_INTERVAL, 0, "" + session.getHeartbeat())); - session.onConnect(this); - initiated = true; - } catch (IOException e) { - outbound.disconnect(); - session.onShutdown(); - } - } else { - outbound.disconnect(); - session.onShutdown(); - } - } else { - List messages = SocketIOFrame.parse(message); - - for (SocketIOFrame msg: messages) { - session.onMessage(msg); - } - } - } - - /* - * (non-Javadoc) - * @see org.eclipse.jetty.websocket.WebSocket#onMessage(byte, byte[], int, int) - */ - @Override - public void onMessage(byte frame, byte[] data, int offset, int length) { - try - { - onMessage(frame,new String(data,offset,length,"UTF-8")); - } - catch(UnsupportedEncodingException e) - { - // Do nothing for now. - } - } - - @Override - public void onFragment(boolean more, byte opcode, byte[] data, int offset, int length) { - throw new UnsupportedOperationException(); - } - - /* - * (non-Javadoc) - * @see com.glines.socketio.SocketIOInbound.SocketIOOutbound#disconnect() - */ - @Override - public void disconnect() { - session.onDisconnect(DisconnectReason.DISCONNECT); - outbound.disconnect(); - } - - @Override - public void close() { - session.startClose(); - } - - @Override - public ConnectionState getConnectionState() { - return session.getConnectionState(); - } - - @Override - public void sendMessage(SocketIOFrame frame) throws SocketIOException { - if (outbound.isOpen()) { - Log.debug("Session["+session.getSessionId()+"]: sendMessage: [" + frame.getFrameType() + "]: " + frame.getData()); - try { - outbound.sendMessage(frame.encode()); - } catch (IOException e) { - outbound.disconnect(); - throw new SocketIOException(e); - } - } else { - throw new SocketIOClosedException(); - } - } - - - /* - * (non-Javadoc) - * @see com.glines.socketio.SocketIOInbound.SocketIOOutbound#sendMessage(java.lang.String) - */ - @Override - public void sendMessage(String message) throws SocketIOException { - sendMessage(SocketIOFrame.TEXT_MESSAGE_TYPE, message); - } - - @Override - public void sendMessage(int messageType, String message) - throws SocketIOException { - if (outbound.isOpen() && session.getConnectionState() == ConnectionState.CONNECTED) { - sendMessage(new SocketIOFrame(SocketIOFrame.FrameType.DATA, messageType, message)); - } else { - throw new SocketIOClosedException(); - } - } - - /* - * (non-Javadoc) - * @see com.glines.socketio.SocketIOSession.SessionTransportHandler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, com.glines.socketio.SocketIOSession) - */ - @Override - public void handle(HttpServletRequest request, - HttpServletResponse response, SocketIOSession session) throws IOException { - response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Unexpected request on upgraded WebSocket connection"); - return; - } - - @Override - public void disconnectWhenEmpty() { - } - - @Override - public void abort() { - outbound.disconnect(); - outbound = null; - session.onShutdown(); - } - } - - public WebSocketTransport(int bufferSize, int maxIdleTime) { - wsFactory = new WebSocketFactory(); - wsFactory.setBufferSize(bufferSize); - wsFactory.setMaxIdleTime(maxIdleTime); - this.maxIdleTime = maxIdleTime; - } - - @Override - public String getName() { - return TRANSPORT_NAME; - } - - @Override - public void handle(HttpServletRequest request, - HttpServletResponse response, - Transport.InboundFactory inboundFactory, - SocketIOSession.Factory sessionFactory) - throws IOException { - - String sessionId = extractSessionId(request); - - if ("GET".equals(request.getMethod()) && sessionId == null && "WebSocket".equals(request.getHeader("Upgrade"))) { - boolean hixie = request.getHeader("Sec-WebSocket-Key1") != null; - - String protocol=request.getHeader(hixie ? "Sec-WebSocket-Protocol" : "WebSocket-Protocol"); - if (protocol == null) - protocol=request.getHeader("Sec-WebSocket-Protocol"); - - String host=request.getHeader("Host"); - String origin=request.getHeader("Origin"); - if (origin == null) { - origin = host; - } - - SocketIOInbound inbound = inboundFactory.getInbound(request); - if (inbound == null) { - if (hixie) { - response.setHeader("Connection","close"); - } - response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE); - } else { - SocketIOSession session = sessionFactory.createSession(inbound); - SessionWrapper wrapper = new SessionWrapper(session); - wsFactory.upgrade(request,response,wrapper,origin,protocol); - } - } else { - response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid " + TRANSPORT_NAME + " transport request"); - } - } -} diff --git a/core/src/main/java/com/glines/socketio/server/transport/XHRMultipartDataHandler.java b/core/src/main/java/com/glines/socketio/server/transport/XHRMultipartDataHandler.java new file mode 100644 index 0000000..e9080fe --- /dev/null +++ b/core/src/main/java/com/glines/socketio/server/transport/XHRMultipartDataHandler.java @@ -0,0 +1,109 @@ +/** + * The MIT License + * Copyright (c) 2010 Tad Glines + * + * Contributors: Ovea.com, Mycila.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.glines.socketio.server.transport; + +import com.glines.socketio.server.SocketIOFrame; +import com.glines.socketio.server.SocketIOSession; +import com.glines.socketio.util.Web; + +import javax.servlet.ServletOutputStream; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Mathieu Carbou + */ +final class XHRMultipartDataHandler extends AbstractDataHandler { + + private static final Logger LOGGER = Logger.getLogger(XHRMultipartDataHandler.class.getName()); + private static final int MULTIPART_BOUNDARY_LENGTH = 20; + private static final long DEFAULT_HEARTBEAT_DELAY = 15 * 1000; + + private final SocketIOSession session; + + private long hearbeatDelay; + + private final String contentType; + private final String boundarySeperator; + + XHRMultipartDataHandler(SocketIOSession session) { + this.session = session; + String boundary = Web.generateRandomString(MULTIPART_BOUNDARY_LENGTH); + boundarySeperator = "--" + boundary; + contentType = "multipart/x-mixed-replace;boundary=\"" + boundary + "\""; + } + + @Override + protected void init() { + this.hearbeatDelay = getConfig().getHeartbeatDelay(DEFAULT_HEARTBEAT_DELAY); + if (LOGGER.isLoggable(Level.FINE)) + LOGGER.fine(getConfig().getNamespace() + " data handler configuration:\n" + + " - heartbeatDelay=" + hearbeatDelay); + } + + @Override + public boolean isConnectionPersistent() { + return true; + } + + @Override + public void onStartSend(HttpServletResponse response) throws IOException { + response.setContentType(contentType); + response.setHeader("Connection", "keep-alive"); + ServletOutputStream os = response.getOutputStream(); + os.print(boundarySeperator); + response.flushBuffer(); + } + + @Override + public void onWriteData(ServletResponse response, String data) throws IOException { + if (LOGGER.isLoggable(Level.FINE)) + LOGGER.log(Level.FINE, "Session[" + session.getSessionId() + "]: writeData(START): " + data); + ServletOutputStream os = response.getOutputStream(); + os.println("Content-Type: text/plain; charset=utf-8"); + os.println(); + os.write(data.getBytes()); + os.println(); + os.println(boundarySeperator); + response.flushBuffer(); + if (LOGGER.isLoggable(Level.FINE)) + LOGGER.log(Level.FINE, "Session[" + session.getSessionId() + "]: writeData(END): " + data); + } + + @Override + public void onFinishSend(ServletResponse response) throws IOException { + } + + @Override + public void onConnect(HttpServletRequest request, HttpServletResponse response) throws IOException { + onStartSend(response); + onWriteData(response, SocketIOFrame.encode(SocketIOFrame.FrameType.SESSION_ID, 0, session.getSessionId())); + onWriteData(response, SocketIOFrame.encode(SocketIOFrame.FrameType.HEARTBEAT_INTERVAL, 0, "" + hearbeatDelay)); + } +} diff --git a/core/src/main/java/com/glines/socketio/server/transport/XHRMultipartTransport.java b/core/src/main/java/com/glines/socketio/server/transport/XHRMultipartTransport.java index fbf4809..354166c 100644 --- a/core/src/main/java/com/glines/socketio/server/transport/XHRMultipartTransport.java +++ b/core/src/main/java/com/glines/socketio/server/transport/XHRMultipartTransport.java @@ -2,6 +2,8 @@ * The MIT License * Copyright (c) 2010 Tad Glines * + * Contributors: Ovea.com, Mycila.com + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights @@ -22,80 +24,17 @@ */ package com.glines.socketio.server.transport; -import java.io.IOException; -import java.util.Arrays; - -import javax.servlet.ServletOutputStream; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.eclipse.jetty.util.log.Log; - -import com.glines.socketio.server.SocketIOFrame; import com.glines.socketio.server.SocketIOSession; -import com.glines.socketio.server.transport.ConnectionTimeoutPreventor.IdleCheck; - -public class XHRMultipartTransport extends XHRTransport { - public static final String TRANSPORT_NAME = "xhr-multipart"; - private static final int MULTIPART_BOUNDARY_LENGTH = 20; - - private class XHRMultipartSessionHelper extends XHRSessionHelper { - private final String contentType; - private final String boundary; - private final String boundarySeperator; - private final IdleCheck idleCheck; - - XHRMultipartSessionHelper(SocketIOSession session, IdleCheck idleCheck) { - super(session, true); - boundary = session.generateRandomString(MULTIPART_BOUNDARY_LENGTH); - boundarySeperator = "--" + boundary; - contentType = "multipart/x-mixed-replace;boundary=\""+boundary+"\""; - this.idleCheck = idleCheck; - } - - protected void startSend(HttpServletResponse response) throws IOException { - response.setContentType(contentType); - response.setHeader("Connection", "keep-alive"); - ServletOutputStream os = response.getOutputStream(); - os.print(boundarySeperator); - response.flushBuffer(); - } - - protected void writeData(ServletResponse response, String data) throws IOException { - idleCheck.activity(); - Log.debug("Session["+session.getSessionId()+"]: writeData(START): " + data); - ServletOutputStream os = response.getOutputStream(); - os.println("Content-Type: text/plain"); - os.println(); - os.println(data); - os.println(boundarySeperator); - response.flushBuffer(); - Log.debug("Session["+session.getSessionId()+"]: writeData(END): " + data); - } - - protected void finishSend(ServletResponse response) throws IOException { - }; - - protected void customConnect(HttpServletRequest request, - HttpServletResponse response) throws IOException { - startSend(response); - writeData(response, SocketIOFrame.encode(SocketIOFrame.FrameType.SESSION_ID, 0, session.getSessionId())); - writeData(response, SocketIOFrame.encode(SocketIOFrame.FrameType.HEARTBEAT_INTERVAL, 0, "" + HEARTBEAT_DELAY)); - } - } - - public XHRMultipartTransport(int bufferSize, int maxIdleTime) { - super(bufferSize, maxIdleTime); - } - - @Override - public String getName() { - return TRANSPORT_NAME; - } - - protected XHRSessionHelper createHelper(SocketIOSession session) { - IdleCheck idleCheck = ConnectionTimeoutPreventor.newTimeoutPreventor(); - return new XHRMultipartSessionHelper(session, idleCheck); - } +import com.glines.socketio.server.TransportType; + +public class XHRMultipartTransport extends AbstractHttpTransport { + @Override + public TransportType getType() { + return TransportType.XHR_MULTIPART; + } + + @Override + protected DataHandler newDataHandler(SocketIOSession session) { + return new XHRMultipartDataHandler(session); + } } diff --git a/core/src/main/java/com/glines/socketio/server/transport/XHRPollingDataHandler.java b/core/src/main/java/com/glines/socketio/server/transport/XHRPollingDataHandler.java new file mode 100644 index 0000000..b797bc2 --- /dev/null +++ b/core/src/main/java/com/glines/socketio/server/transport/XHRPollingDataHandler.java @@ -0,0 +1,90 @@ +/** + * The MIT License + * Copyright (c) 2010 Tad Glines + * + * Contributors: Ovea.com, Mycila.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.glines.socketio.server.transport; + +import com.glines.socketio.server.SocketIOFrame; +import com.glines.socketio.server.SocketIOSession; + +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Mathieu Carbou + */ +final class XHRPollingDataHandler extends AbstractDataHandler { + + /** + * For non persistent connection transports, this is the amount of time to wait + * for messages before returning empty results. + */ + private static final long DEFAULT_TIMEOUT = 20 * 1000; + private static final Logger LOGGER = Logger.getLogger(XHRPollingDataHandler.class.getName()); + + private final SocketIOSession session; + private long timeout; + + XHRPollingDataHandler(SocketIOSession session) { + this.session = session; + } + + @Override + protected void init() { + this.timeout = getConfig().getTimeout(DEFAULT_TIMEOUT); + if (LOGGER.isLoggable(Level.FINE)) + LOGGER.fine(getConfig().getNamespace() + " data handler configuration:\n" + + " - timeout=" + timeout); + } + + @Override + public boolean isConnectionPersistent() { + return false; + } + + @Override + public void onStartSend(HttpServletResponse response) throws IOException { + response.setContentType("text/plain; charset=UTF-8"); + } + + @Override + public void onWriteData(ServletResponse response, String data) throws IOException { + response.getOutputStream().print(data); + response.flushBuffer(); + } + + @Override + public void onFinishSend(ServletResponse response) throws IOException { + } + + @Override + public void onConnect(HttpServletRequest request, HttpServletResponse response) throws IOException { + onStartSend(response); + onWriteData(response, SocketIOFrame.encode(SocketIOFrame.FrameType.SESSION_ID, 0, session.getSessionId())); + onWriteData(response, SocketIOFrame.encode(SocketIOFrame.FrameType.HEARTBEAT_INTERVAL, 0, "" + timeout)); + } +} diff --git a/core/src/main/java/com/glines/socketio/server/transport/XHRPollingTransport.java b/core/src/main/java/com/glines/socketio/server/transport/XHRPollingTransport.java index b3e6c74..6021519 100644 --- a/core/src/main/java/com/glines/socketio/server/transport/XHRPollingTransport.java +++ b/core/src/main/java/com/glines/socketio/server/transport/XHRPollingTransport.java @@ -2,6 +2,8 @@ * The MIT License * Copyright (c) 2010 Tad Glines * + * Contributors: Ovea.com, Mycila.com + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights @@ -22,55 +24,17 @@ */ package com.glines.socketio.server.transport; -import java.io.IOException; - -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import com.glines.socketio.server.SocketIOFrame; import com.glines.socketio.server.SocketIOSession; - -public class XHRPollingTransport extends XHRTransport { - public static final String TRANSPORT_NAME = "xhr-polling"; - - protected class XHRPollingSessionHelper extends XHRSessionHelper { - - XHRPollingSessionHelper(SocketIOSession session) { - super(session, false); - } - - protected void startSend(HttpServletResponse response) throws IOException { - response.setContentType("text/plain; charset=UTF-8"); - } - - @Override - protected void writeData(ServletResponse response, String data) throws IOException { - response.getOutputStream().print(data); - response.flushBuffer(); - } - - protected void finishSend(ServletResponse response) throws IOException {}; - - protected void customConnect(HttpServletRequest request, - HttpServletResponse response) throws IOException { - startSend(response); - writeData(response, SocketIOFrame.encode(SocketIOFrame.FrameType.SESSION_ID, 0, session.getSessionId())); - writeData(response, SocketIOFrame.encode(SocketIOFrame.FrameType.HEARTBEAT_INTERVAL, 0, "" + REQUEST_TIMEOUT)); - } - } - - public XHRPollingTransport(int bufferSize, int maxIdleTime) { - super(bufferSize, maxIdleTime); - } - - @Override - public String getName() { - return TRANSPORT_NAME; - } - - - protected XHRPollingSessionHelper createHelper(SocketIOSession session) { - return new XHRPollingSessionHelper(session); - } +import com.glines.socketio.server.TransportType; + +public class XHRPollingTransport extends AbstractHttpTransport { + @Override + public TransportType getType() { + return TransportType.XHR_POLLING; + } + + @Override + protected DataHandler newDataHandler(SocketIOSession session) { + return new XHRPollingDataHandler(session); + } } diff --git a/core/src/main/java/com/glines/socketio/server/transport/XHRTransport.java b/core/src/main/java/com/glines/socketio/server/transport/XHRTransport.java deleted file mode 100644 index 22e2960..0000000 --- a/core/src/main/java/com/glines/socketio/server/transport/XHRTransport.java +++ /dev/null @@ -1,402 +0,0 @@ -/** - * The MIT License - * Copyright (c) 2010 Tad Glines - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.glines.socketio.server.transport; - -import java.io.BufferedReader; -import java.io.IOException; -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; - -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.eclipse.jetty.continuation.Continuation; -import org.eclipse.jetty.continuation.ContinuationListener; -import org.eclipse.jetty.continuation.ContinuationSupport; -import org.eclipse.jetty.util.IO; -import org.eclipse.jetty.util.URIUtil; -import org.eclipse.jetty.util.log.Log; - -import com.glines.socketio.common.ConnectionState; -import com.glines.socketio.common.DisconnectReason; -import com.glines.socketio.common.SocketIOException; -import com.glines.socketio.server.SocketIOClosedException; -import com.glines.socketio.server.SocketIOInbound; -import com.glines.socketio.server.SocketIOFrame; -import com.glines.socketio.server.SocketIOSession; -import com.glines.socketio.server.SocketIOSession.SessionTransportHandler; -import com.glines.socketio.server.Transport; - -public abstract class XHRTransport extends AbstractHttpTransport { - public static final String CONTINUATION_KEY = - "com.glines.socketio.server.transport.XHRTransport.Continuation"; - private final int bufferSize; - private final int maxIdleTime; - - protected abstract class XHRSessionHelper - implements SessionTransportHandler, ContinuationListener { - protected final SocketIOSession session; - private final TransportBuffer buffer = new TransportBuffer(bufferSize); - private volatile boolean is_open = false; - private volatile Continuation continuation = null; - private final boolean isConnectionPersistant; - private boolean disconnectWhenEmpty = false; - - XHRSessionHelper(SocketIOSession session, boolean isConnectionPersistant) { - this.session = session; - this.isConnectionPersistant = isConnectionPersistant; - if (isConnectionPersistant) { - session.setHeartbeat(HEARTBEAT_DELAY); - session.setTimeout(HEARTBEAT_TIMEOUT); - } else { - session.setTimeout((HTTP_REQUEST_TIMEOUT-REQUEST_TIMEOUT)/2); - } - } - - protected abstract void startSend(HttpServletResponse response) throws IOException; - - protected abstract void writeData(ServletResponse response, String data) throws IOException; - - protected abstract void finishSend(ServletResponse response) throws IOException; - - @Override - public void disconnect() { - synchronized (this) { - session.onDisconnect(DisconnectReason.DISCONNECT); - abort(); - } - } - - @Override - public void close() { - synchronized (this) { - session.startClose(); - } - } - - @Override - public ConnectionState getConnectionState() { - return session.getConnectionState(); - } - - @Override - public void sendMessage(SocketIOFrame frame) - throws SocketIOException { - synchronized (this) { - Log.debug("Session["+session.getSessionId()+"]: " + - "sendMessage(frame): [" + frame.getFrameType() + "]: " + frame.getData()); - if (is_open) { - if (continuation != null) { - List messages = buffer.drainMessages(); - messages.add(frame.encode()); - StringBuilder data = new StringBuilder(); - for (String msg: messages) { - data.append(msg); - } - try { - writeData(continuation.getServletResponse(), data.toString()); - } catch (IOException e) { - throw new SocketIOException(e); - } - if (!isConnectionPersistant && !continuation.isInitial()) { - Continuation cont = continuation; - continuation = null; - cont.complete(); - } else { - session.startHeartbeatTimer(); - } - } else { - String data = frame.encode(); - if (buffer.putMessage(data, maxIdleTime) == false) { - session.onDisconnect(DisconnectReason.TIMEOUT); - abort(); - throw new SocketIOException(); - } - } - } else { - throw new SocketIOClosedException(); - } - } - } - - @Override - public void sendMessage(String message) throws SocketIOException { - Log.debug("Session["+session.getSessionId()+"]: " + - "sendMessage(String): " + message); - sendMessage(SocketIOFrame.TEXT_MESSAGE_TYPE, message); - } - - @Override - public void sendMessage(int messageType, String message) - throws SocketIOException { - synchronized (this) { - Log.debug("Session["+session.getSessionId()+"]: " + - "sendMessage(int, String): [" + messageType + "]: " + message); - if (is_open && session.getConnectionState() == ConnectionState.CONNECTED) { - sendMessage(new SocketIOFrame(SocketIOFrame.FrameType.DATA, messageType, message)); - } else { - throw new SocketIOClosedException(); - } - } - } - - @Override - public void handle(HttpServletRequest request, - HttpServletResponse response, SocketIOSession session) - throws IOException { - if ("GET".equals(request.getMethod())) { - synchronized (this) { - if (!is_open && buffer.isEmpty()) { - response.sendError(HttpServletResponse.SC_NOT_FOUND); - } else { - /* - */ - Continuation cont = (Continuation)request.getAttribute(CONTINUATION_KEY); - if (continuation != null || cont != null) { - if (continuation == cont) { - continuation = null; - finishSend(response); - } - if (cont != null) { - request.removeAttribute(CONTINUATION_KEY); - } - return; - } - if (!isConnectionPersistant) { - if (!buffer.isEmpty()) { - List messages = buffer.drainMessages(); - if (messages.size() > 0) { - StringBuilder data = new StringBuilder(); - for (String msg: messages) { - data.append(msg); - } - startSend(response); - writeData(response, data.toString()); - finishSend(response); - if (!disconnectWhenEmpty) { - session.startTimeoutTimer(); - } else { - abort(); - } - } - } else { - session.clearTimeoutTimer(); - request.setAttribute(SESSION_KEY, session); - response.setBufferSize(bufferSize); - continuation = ContinuationSupport.getContinuation(request); - continuation.addContinuationListener(this); - continuation.setTimeout(REQUEST_TIMEOUT); - continuation.suspend(response); - request.setAttribute(CONTINUATION_KEY, continuation); - startSend(response); - } - } else { - response.sendError(HttpServletResponse.SC_NOT_FOUND); - } - } - } - } else if ("POST".equals(request.getMethod())) { - if (is_open) { - int size = request.getContentLength(); - BufferedReader reader = request.getReader(); - if (size == 0) { - response.sendError(HttpServletResponse.SC_BAD_REQUEST); - } else { - String data = decodePostData(request.getContentType(), IO.toString(reader)); - if (data != null && data.length() > 0) { - List list = SocketIOFrame.parse(data); - synchronized (session) { - for (SocketIOFrame msg: list) { - session.onMessage(msg); - } - } - } - // Ensure that the disconnectWhenEmpty flag is obeyed in the case where - // it is set during a POST. - synchronized (this) { - if (disconnectWhenEmpty && buffer.isEmpty()) { - if (session.getConnectionState() == ConnectionState.CLOSING) { - session.onDisconnect(DisconnectReason.CLOSED); - } - abort(); - } - } - } - } - } else { - response.sendError(HttpServletResponse.SC_BAD_REQUEST); - } - - } - - protected String decodePostData(String contentType, String data) { - if (contentType.startsWith("application/x-www-form-urlencoded")) { - if (data.substring(0, 5).equals("data=")) { - return URIUtil.decodePath(data.substring(5)); - } else { - return ""; - } - } else if (contentType.startsWith("text/plain")) { - return data; - } else { - // TODO: Treat as text for now, maybe error in the future. - return data; - } - } - - @Override - public void onComplete(Continuation cont) { - if (continuation != null && cont == continuation) { - continuation = null; - if (isConnectionPersistant) { - is_open = false; - if (!disconnectWhenEmpty) { - session.onDisconnect(DisconnectReason.DISCONNECT); - } - abort(); - } else { - if (!is_open && buffer.isEmpty() && !disconnectWhenEmpty) { - session.onDisconnect(DisconnectReason.DISCONNECT); - abort(); - } else { - if (disconnectWhenEmpty) { - abort(); - } else { - session.startTimeoutTimer(); - } - } - } - } - } - - @Override - public void onTimeout(Continuation cont) { - if (continuation != null && cont == continuation) { - continuation = null; - if (isConnectionPersistant) { - is_open = false; - session.onDisconnect(DisconnectReason.TIMEOUT); - abort(); - } else { - if (!is_open && buffer.isEmpty()) { - session.onDisconnect(DisconnectReason.DISCONNECT); - abort(); - } else { - try { - finishSend(cont.getServletResponse()); - } catch (IOException e) { - session.onDisconnect(DisconnectReason.DISCONNECT); - abort(); - } - } - session.startTimeoutTimer(); - } - } - } - - protected abstract void customConnect(HttpServletRequest request, - HttpServletResponse response) throws IOException; - - public void connect(HttpServletRequest request, - HttpServletResponse response) throws IOException { - request.setAttribute(SESSION_KEY, session); - response.setBufferSize(bufferSize); - continuation = ContinuationSupport.getContinuation(request); - continuation.addContinuationListener(this); - if (isConnectionPersistant) { - continuation.setTimeout(0); - } - customConnect(request, response); - is_open = true; - session.onConnect(this); - finishSend(response); - if (continuation != null) { - if (isConnectionPersistant) { - request.setAttribute(CONTINUATION_KEY, continuation); - continuation.suspend(response); - } else { - continuation = null; - } - } - } - - @Override - public void disconnectWhenEmpty() { - disconnectWhenEmpty = true; - } - - @Override - public void abort() { - session.clearHeartbeatTimer(); - session.clearTimeoutTimer(); - is_open = false; - if (continuation != null) { - Continuation cont = continuation; - continuation = null; - if (cont.isSuspended()) { - cont.complete(); - } - } - buffer.setListener(new TransportBuffer.BufferListener() { - @Override - public boolean onMessages(List messages) { - return false; - } - - @Override - public boolean onMessage(String message) { - return false; - } - }); - buffer.clear(); - session.onShutdown(); - } - } - - public XHRTransport(int bufferSize, int maxIdleTime) { - this.bufferSize = bufferSize; - this.maxIdleTime = maxIdleTime; - } - - /** - * This method should only be called within the context of an active HTTP request. - */ - protected abstract XHRSessionHelper createHelper(SocketIOSession session); - - @Override - protected SocketIOSession connect(HttpServletRequest request, - HttpServletResponse response, Transport.InboundFactory inboundFactory, - com.glines.socketio.server.SocketIOSession.Factory sessionFactory) - throws IOException { - SocketIOInbound inbound = inboundFactory.getInbound(request); - if (inbound != null) { - SocketIOSession session = sessionFactory.createSession(inbound); - XHRSessionHelper handler = createHelper(session); - handler.connect(request, response); - return session; - } - return null; - } - -} diff --git a/core/src/main/java/com/glines/socketio/util/DefaultLoader.java b/core/src/main/java/com/glines/socketio/util/DefaultLoader.java new file mode 100644 index 0000000..b774f76 --- /dev/null +++ b/core/src/main/java/com/glines/socketio/util/DefaultLoader.java @@ -0,0 +1,86 @@ +/** + * The MIT License + * Copyright (c) 2010 Tad Glines + * + * Contributors: Ovea.com, Mycila.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.glines.socketio.util; + +import java.io.IOException; +import java.net.URL; +import java.util.Enumeration; +import java.util.LinkedList; +import java.util.List; + +/** + * @author Mathieu Carbou (mathieu.carbou@gmail.com) + */ +public class DefaultLoader implements Loader { + + private final ClassLoader classLoader; + + public DefaultLoader() { + this(getDefaultClassLoader()); + } + + public DefaultLoader(ClassLoader classLoader) { + this.classLoader = classLoader; + } + + @Override + public Class loadClass(String className) { + try { + return classLoader.loadClass(className); + } catch (ClassNotFoundException e) { + throw new IllegalArgumentException("Unable to load class " + className, e); + } + } + + @Override + public URL getResource(String path) { + return classLoader.getResource(path); + } + + @Override + public List getResources(String path) { + List urls = new LinkedList(); + try { + Enumeration e = classLoader.getResources(path); + while (e.hasMoreElements()) + urls.add(e.nextElement()); + } catch (IOException ignored) { + } + return urls; + } + + private static ClassLoader getDefaultClassLoader() { + ClassLoader cl = null; + try { + cl = Thread.currentThread().getContextClassLoader(); + } + catch (Throwable ignored) { + } + if (cl == null) + cl = DefaultLoader.class.getClassLoader(); + return cl; + } + +} \ No newline at end of file diff --git a/core/src/main/java/com/glines/socketio/util/IO.java b/core/src/main/java/com/glines/socketio/util/IO.java new file mode 100644 index 0000000..cb18dd4 --- /dev/null +++ b/core/src/main/java/com/glines/socketio/util/IO.java @@ -0,0 +1,65 @@ +/** + * The MIT License + * Copyright (c) 2010 Tad Glines + * + * Contributors: Ovea.com, Mycila.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.glines.socketio.util; + +import java.io.*; + +/** + * @author Mathieu Carbou + */ +public final class IO { + private IO() { + } + + public static void copy(InputStream is, OutputStream os) throws IOException { + byte[] buffer = new byte[64 * 1024]; + int len; + while ((len = is.read(buffer)) >= 0) + os.write(buffer, 0, len); + } + + public static void copy(Reader in, Writer out) throws IOException { + char[] buffer = new char[64 * 1024]; + int len; + while ((len = in.read(buffer)) >= 0) + out.write(buffer, 0, len); + } + + public static String toString(InputStream in) throws IOException { + return toString(in, null); + } + + public static String toString(Reader in) throws IOException { + StringWriter writer = new StringWriter(); + copy(in, writer); + return writer.toString(); + } + + public static String toString(InputStream in, String encoding) throws IOException { + InputStreamReader reader = encoding == null ? new InputStreamReader(in) : new InputStreamReader(in, encoding); + return toString(reader); + } + +} diff --git a/core/src/main/java/com/glines/socketio/util/JSON.java b/core/src/main/java/com/glines/socketio/util/JSON.java new file mode 100644 index 0000000..174f88c --- /dev/null +++ b/core/src/main/java/com/glines/socketio/util/JSON.java @@ -0,0 +1,1116 @@ +/** + * The MIT License + * Copyright (c) 2010 Tad Glines + * + * Contributors: Ovea.com, Mycila.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.glines.socketio.util; + +import java.io.IOException; +import java.io.Reader; +import java.lang.reflect.Array; +import java.util.*; + +public final class JSON { + + private static final int BUFFER = 1024; + + private JSON() { + } + + public static String toString(Object object) { + StringBuilder buffer = new StringBuilder(BUFFER); + append(buffer, object); + return buffer.toString(); + } + + + public static String toString(Map object) { + StringBuilder buffer = new StringBuilder(BUFFER); + appendMap(buffer, object); + return buffer.toString(); + } + + + public static String toString(Object[] array) { + StringBuilder buffer = new StringBuilder(BUFFER); + appendArray(buffer, array); + return buffer.toString(); + } + + + /** + * @param s String containing JSON object or array. + * @return A Map, Object array or primitive array parsed from the JSON. + */ + public static Object parse(String s) { + return parse(new StringSource(s), false); + } + + + /** + * @param s String containing JSON object or array. + * @param stripOuterComment If true, an outer comment around the JSON is ignored. + * @return A Map, Object array or primitive array parsed from the JSON. + */ + public static Object parse(String s, boolean stripOuterComment) { + return parse(new StringSource(s), stripOuterComment); + } + + + /** + * @param in Reader containing JSON object or array. + * @return A Map, Object array or primitive array parsed from the JSON. + * @throws java.io.IOException - + */ + public static Object parse(Reader in) throws IOException { + return parse(new ReaderSource(in), false); + } + + + /** + * @param in Reader containing JSON object or array. + * @param stripOuterComment If true, an outer comment around the JSON is ignored. + * @return A Map, Object array or primitive array parsed from the JSON. + * @throws java.io.IOException - + */ + public static Object parse(Reader in, boolean stripOuterComment) throws IOException { + return parse(new ReaderSource(in), stripOuterComment); + } + + + /** + * Append object as JSON to string buffer. + * + * @param buffer the buffer to append to + * @param object the object to append + */ + public static void append(Appendable buffer, Object object) { + try { + if (object == null) + buffer.append("null"); + else if (object instanceof Convertible) + appendJSON(buffer, (Convertible) object); + else if (object instanceof Generator) + appendJSON(buffer, (Generator) object); + else if (object instanceof Map) + appendMap(buffer, (Map) object); + else if (object instanceof Collection) + appendArray(buffer, (Collection) object); + else if (object.getClass().isArray()) + appendArray(buffer, object); + else if (object instanceof Number) + appendNumber(buffer, (Number) object); + else if (object instanceof Boolean) + appendBoolean(buffer, (Boolean) object); + else if (object instanceof Character) + appendString(buffer, object.toString()); + else if (object instanceof String) + appendString(buffer, (String) object); + else { + appendString(buffer, object.toString()); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + + public static void appendNull(Appendable buffer) { + try { + buffer.append("null"); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + + public static void appendJSON(final Appendable buffer, Convertible converter) { + ConvertableOutput out = new ConvertableOutput(buffer); + converter.toJSON(out); + out.complete(); + } + + + public static void appendJSON(Appendable buffer, Generator generator) { + generator.addJSON(buffer); + } + + + public static void appendMap(Appendable buffer, Map map) { + try { + if (map == null) { + appendNull(buffer); + return; + } + + buffer.append('{'); + Iterator iter = map.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry entry = (Map.Entry) iter.next(); + quote(buffer, entry.getKey().toString()); + buffer.append(':'); + append(buffer, entry.getValue()); + if (iter.hasNext()) + buffer.append(','); + } + + buffer.append('}'); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + + public static void appendArray(Appendable buffer, Collection collection) { + try { + if (collection == null) { + appendNull(buffer); + return; + } + + buffer.append('['); + Iterator iter = collection.iterator(); + boolean first = true; + while (iter.hasNext()) { + if (!first) + buffer.append(','); + + first = false; + append(buffer, iter.next()); + } + + buffer.append(']'); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + + public static void appendArray(Appendable buffer, Object array) { + try { + if (array == null) { + appendNull(buffer); + return; + } + + buffer.append('['); + int length = Array.getLength(array); + + for (int i = 0; i < length; i++) { + if (i != 0) + buffer.append(','); + append(buffer, Array.get(array, i)); + } + + buffer.append(']'); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + + public static void appendBoolean(Appendable buffer, Boolean b) { + try { + if (b == null) { + appendNull(buffer); + return; + } + buffer.append(b ? "true" : "false"); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + + public static void appendNumber(Appendable buffer, Number number) { + try { + if (number == null) { + appendNull(buffer); + return; + } + buffer.append(String.valueOf(number)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + + public static void appendString(Appendable buffer, String string) { + if (string == null) { + appendNull(buffer); + return; + } + quote(buffer, string); + } + + // Parsing utilities + + + protected static String toString(char[] buffer, int offset, int length) { + return new String(buffer, offset, length); + } + + + protected static Map newMap() { + return new HashMap(); + } + + + protected static Object[] newArray(int size) { + return new Object[size]; + } + + protected static Object convertTo(Class type, Map map) { + if (type != null && Convertible.class.isAssignableFrom(type)) { + try { + Convertible conv = (Convertible) type.newInstance(); + conv.fromJSON(map); + return conv; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + return map; + } + + + public static Object parse(Source source, boolean stripOuterComment) { + int comment_state = 0; // 0=no comment, 1="/", 2="/*", 3="/* *" -1="//" + if (!stripOuterComment) + return parse(source); + + int strip_state = 1; // 0=no strip, 1=wait for /*, 2= wait for */ + + Object o = null; + while (source.hasNext()) { + char c = source.peek(); + + // handle // or /* comment + if (comment_state == 1) { + switch (c) { + case '/': + comment_state = -1; + break; + case '*': + comment_state = 2; + if (strip_state == 1) { + comment_state = 0; + strip_state = 2; + } + } + } + // handle /* */ comment + else if (comment_state > 1) { + switch (c) { + case '*': + comment_state = 3; + break; + case '/': + if (comment_state == 3) { + comment_state = 0; + if (strip_state == 2) + return o; + } else + comment_state = 2; + break; + default: + comment_state = 2; + } + } + // handle // comment + else if (comment_state < 0) { + switch (c) { + case '\r': + case '\n': + comment_state = 0; + default: + break; + } + } + // handle unknown + else { + if (!Character.isWhitespace(c)) { + if (c == '/') + comment_state = 1; + else if (c == '*') + comment_state = 3; + else if (o == null) { + o = parse(source); + continue; + } + } + } + + source.next(); + } + + return o; + } + + + public static Object parse(Source source) { + int comment_state = 0; // 0=no comment, 1="/", 2="/*", 3="/* *" -1="//" + + while (source.hasNext()) { + char c = source.peek(); + + // handle // or /* comment + if (comment_state == 1) { + switch (c) { + case '/': + comment_state = -1; + break; + case '*': + comment_state = 2; + } + } + // handle /* */ comment + else if (comment_state > 1) { + switch (c) { + case '*': + comment_state = 3; + break; + case '/': + if (comment_state == 3) + comment_state = 0; + else + comment_state = 2; + break; + default: + comment_state = 2; + } + } + // handle // comment + else if (comment_state < 0) { + switch (c) { + case '\r': + case '\n': + comment_state = 0; + break; + default: + break; + } + } + // handle unknown + else { + switch (c) { + case '{': + return parseObject(source); + case '[': + return parseArray(source); + case '"': + return parseString(source); + case '-': + return parseNumber(source); + + case 'n': + complete("null", source); + return null; + case 't': + complete("true", source); + return Boolean.TRUE; + case 'f': + complete("false", source); + return Boolean.FALSE; + case 'u': + complete("undefined", source); + return null; + case 'N': + complete("NaN", source); + return null; + + case '/': + comment_state = 1; + break; + + default: + if (Character.isDigit(c)) + return parseNumber(source); + else if (Character.isWhitespace(c)) + break; + return handleUnknown(source, c); + } + } + source.next(); + } + + return null; + } + + + protected static Object handleUnknown(Source source, char c) { + throw new IllegalStateException("unknown char '" + c + "'(" + (int) c + ") in " + source); + } + + + private static Object parseObject(Source source) { + if (source.next() != '{') + throw new IllegalStateException(); + Map map = newMap(); + + char next = seekTo("\"}", source); + + while (source.hasNext()) { + if (next == '}') { + source.next(); + break; + } + + String name = parseString(source); + seekTo(':', source); + source.next(); + + Object value = parse(source); + map.put(name, value); + + seekTo(",}", source); + next = source.next(); + if (next == '}') + break; + else + next = seekTo("\"}", source); + } + + String classname = (String) map.get("class"); + if (classname != null) { + try { + Class c = Thread.currentThread().getContextClassLoader().loadClass(classname); + return convertTo(c, map); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + } + return map; + } + + + protected static Object parseArray(Source source) { + if (source.next() != '[') + throw new IllegalStateException(); + + int size = 0; + ArrayList list = null; + Object item = null; + boolean coma = true; + + while (source.hasNext()) { + char c = source.peek(); + switch (c) { + case ']': + source.next(); + switch (size) { + case 0: + return newArray(0); + case 1: + Object array = newArray(1); + Array.set(array, 0, item); + return array; + default: + return list.toArray(newArray(list.size())); + } + + case ',': + if (coma) + throw new IllegalStateException(); + coma = true; + source.next(); + break; + + default: + if (Character.isWhitespace(c)) + source.next(); + else { + coma = false; + if (size++ == 0) + item = parse(source); + else if (list == null) { + list = new ArrayList(); + list.add(item); + item = parse(source); + list.add(item); + item = null; + } else { + item = parse(source); + list.add(item); + item = null; + } + } + } + + } + + throw new IllegalStateException("unexpected end of array"); + } + + + protected static String parseString(Source source) { + if (source.next() != '"') + throw new IllegalStateException(); + + boolean escape = false; + + StringBuilder b = null; + final char[] scratch = source.scratchBuffer(); + + if (scratch != null) { + int i = 0; + while (source.hasNext()) { + if (i >= scratch.length) { + // we have filled the scratch buffer, so we must + // use the StringBuffer for a large string + b = new StringBuilder(scratch.length * 2); + b.append(scratch, 0, i); + break; + } + + char c = source.next(); + + if (escape) { + escape = false; + switch (c) { + case '"': + scratch[i++] = '"'; + break; + case '\\': + scratch[i++] = '\\'; + break; + case '/': + scratch[i++] = '/'; + break; + case 'b': + scratch[i++] = '\b'; + break; + case 'f': + scratch[i++] = '\f'; + break; + case 'n': + scratch[i++] = '\n'; + break; + case 'r': + scratch[i++] = '\r'; + break; + case 't': + scratch[i++] = '\t'; + break; + case 'u': + char uc = (char) ((convertHexDigit((byte) source.next()) << 12) + (convertHexDigit((byte) source.next()) << 8) + + (convertHexDigit((byte) source.next()) << 4) + (convertHexDigit((byte) source.next()))); + scratch[i++] = uc; + break; + default: + scratch[i++] = c; + } + } else if (c == '\\') { + escape = true; + } else if (c == '\"') { + // Return string that fits within scratch buffer + return toString(scratch, 0, i); + } else + scratch[i++] = c; + } + + // Missing end quote, but return string anyway ? + if (b == null) + return toString(scratch, 0, i); + } else + b = new StringBuilder(BUFFER); + + // parse large string into string buffer + final StringBuilder builder = b; + while (source.hasNext()) { + char c = source.next(); + + if (escape) { + escape = false; + switch (c) { + case '"': + builder.append('"'); + break; + case '\\': + builder.append('\\'); + break; + case '/': + builder.append('/'); + break; + case 'b': + builder.append('\b'); + break; + case 'f': + builder.append('\f'); + break; + case 'n': + builder.append('\n'); + break; + case 'r': + builder.append('\r'); + break; + case 't': + builder.append('\t'); + break; + case 'u': + char uc = (char) ((convertHexDigit((byte) source.next()) << 12) + (convertHexDigit((byte) source.next()) << 8) + + (convertHexDigit((byte) source.next()) << 4) + (convertHexDigit((byte) source.next()))); + builder.append(uc); + break; + default: + builder.append(c); + } + } else if (c == '\\') { + escape = true; + } else if (c == '\"') + break; + else + builder.append(c); + } + return builder.toString(); + } + + + public static Number parseNumber(Source source) { + boolean minus = false; + long number = 0; + StringBuilder buffer = null; + + longLoop: + while (source.hasNext()) { + char c = source.peek(); + switch (c) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + number = number * 10 + (c - '0'); + source.next(); + break; + + case '-': + case '+': + if (number != 0) + throw new IllegalStateException("bad number"); + minus = true; + source.next(); + break; + + case '.': + case 'e': + case 'E': + buffer = new StringBuilder(16); + if (minus) + buffer.append('-'); + buffer.append(number); + buffer.append(c); + source.next(); + break longLoop; + + default: + break longLoop; + } + } + + if (buffer == null) + return minus ? -1 * number : number; + + doubleLoop: + while (source.hasNext()) { + char c = source.peek(); + switch (c) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + case '.': + case '+': + case 'e': + case 'E': + buffer.append(c); + source.next(); + break; + + default: + break doubleLoop; + } + } + return new Double(buffer.toString()); + + } + + + protected static void seekTo(char seek, Source source) { + while (source.hasNext()) { + char c = source.peek(); + if (c == seek) + return; + + if (!Character.isWhitespace(c)) + throw new IllegalStateException("Unexpected '" + c + " while seeking '" + seek + "'"); + source.next(); + } + + throw new IllegalStateException("Expected '" + seek + "'"); + } + + + protected static char seekTo(String seek, Source source) { + while (source.hasNext()) { + char c = source.peek(); + if (seek.indexOf(c) >= 0) { + return c; + } + + if (!Character.isWhitespace(c)) + throw new IllegalStateException("Unexpected '" + c + "' while seeking one of '" + seek + "'"); + source.next(); + } + + throw new IllegalStateException("Expected one of '" + seek + "'"); + } + + + protected static void complete(String seek, Source source) { + int i = 0; + while (source.hasNext() && i < seek.length()) { + char c = source.next(); + if (c != seek.charAt(i++)) + throw new IllegalStateException("Unexpected '" + c + " while seeking \"" + seek + "\""); + } + + if (i < seek.length()) + throw new IllegalStateException("Expected \"" + seek + "\""); + } + + private static final class ConvertableOutput implements Output { + private final Appendable _buffer; + char c = '{'; + + private ConvertableOutput(Appendable buffer) { + _buffer = buffer; + } + + public void complete() { + try { + if (c == '{') + _buffer.append("{}"); + else if (c != 0) + _buffer.append("}"); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public void add(Object obj) { + if (c == 0) + throw new IllegalStateException(); + append(_buffer, obj); + c = 0; + } + + public void addClass(Class type) { + try { + if (c == 0) + throw new IllegalStateException(); + _buffer.append(c); + _buffer.append("\"class\":"); + append(_buffer, type.getName()); + c = ','; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public void add(String name, Object value) { + try { + if (c == 0) + throw new IllegalStateException(); + _buffer.append(c); + quote(_buffer, name); + _buffer.append(':'); + append(_buffer, value); + c = ','; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public void add(String name, double value) { + try { + if (c == 0) + throw new IllegalStateException(); + _buffer.append(c); + quote(_buffer, name); + _buffer.append(':'); + appendNumber(_buffer, value); + c = ','; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public void add(String name, long value) { + try { + if (c == 0) + throw new IllegalStateException(); + _buffer.append(c); + quote(_buffer, name); + _buffer.append(':'); + appendNumber(_buffer, value); + c = ','; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public void add(String name, boolean value) { + try { + if (c == 0) + throw new IllegalStateException(); + _buffer.append(c); + quote(_buffer, name); + _buffer.append(':'); + appendBoolean(_buffer, value ? Boolean.TRUE : Boolean.FALSE); + c = ','; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + + public static interface Source { + boolean hasNext(); + + char next(); + + char peek(); + + char[] scratchBuffer(); + } + + + public static class StringSource implements Source { + private final String string; + private int index; + private char[] scratch; + + public StringSource(String s) { + string = s; + } + + public boolean hasNext() { + if (index < string.length()) + return true; + scratch = null; + return false; + } + + public char next() { + return string.charAt(index++); + } + + public char peek() { + return string.charAt(index); + } + + @Override + public String toString() { + return string.substring(0, index) + "|||" + string.substring(index); + } + + public char[] scratchBuffer() { + if (scratch == null) + scratch = new char[string.length()]; + return scratch; + } + } + + + public static class ReaderSource implements Source { + private Reader _reader; + private int _next = -1; + private char[] scratch; + + public ReaderSource(Reader r) { + _reader = r; + } + + public boolean hasNext() { + getNext(); + if (_next < 0) { + scratch = null; + return false; + } + return true; + } + + public char next() { + getNext(); + char c = (char) _next; + _next = -1; + return c; + } + + public char peek() { + getNext(); + return (char) _next; + } + + private void getNext() { + if (_next < 0) { + try { + _next = _reader.read(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + public char[] scratchBuffer() { + if (scratch == null) + scratch = new char[1024]; + return scratch; + } + + } + + /** + * JSON Output class for use by {@link Convertible}. + */ + public static interface Output { + public void addClass(Class c); + + public void add(Object obj); + + public void add(String name, Object value); + + public void add(String name, double value); + + public void add(String name, long value); + + public void add(String name, boolean value); + } + + + /** + * JSON Convertible object. Object can implement this interface in a similar + * way to the {@link java.io.Externalizable} interface is used to allow classes to + * provide their own serialization mechanism. + *

+ * A JSON.Convertible object may be written to a JSONObject or initialized + * from a Map of field names to values. + *

+ * If the JSON is to be convertible back to an Object, then the method + * {@link Output#addClass(Class)} must be called from within toJSON() + */ + public static interface Convertible { + public void toJSON(Output out); + + public void fromJSON(Map object); + } + + + /** + * JSON Generator. A class that can add it's JSON representation directly to + * a StringBuffer. This is useful for object instances that are frequently + * converted and wish to avoid multiple Conversions + */ + public interface Generator { + public void addJSON(Appendable buffer); + } + + /** + * Quote a string into an Appendable. + * The characters ", \, \n, \r, \t, \f and \b are escaped + * + * @param buf The Appendable + * @param s The String to quote. + */ + private static void quote(Appendable buf, String s) { + try { + buf.append('"'); + + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + switch (c) { + case '"': + buf.append("\\\""); + continue; + case '\\': + buf.append("\\\\"); + continue; + case '\n': + buf.append("\\n"); + continue; + case '\r': + buf.append("\\r"); + continue; + case '\t': + buf.append("\\t"); + continue; + case '\f': + buf.append("\\f"); + continue; + case '\b': + buf.append("\\b"); + continue; + + default: + if (c < 0x10) { + buf.append("\\u000"); + buf.append(Integer.toString(c, 16)); + } else if (c <= 0x1f) { + buf.append("\\u00"); + buf.append(Integer.toString(c, 16)); + } else + buf.append(c); + } + } + + buf.append('"'); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * @param b An ASCII encoded character 0-9 a-f A-F + * @return The byte value of the character 0-16. + */ + private static byte convertHexDigit(byte b) { + if ((b >= '0') && (b <= '9')) return (byte) (b - '0'); + if ((b >= 'a') && (b <= 'f')) return (byte) (b - 'a' + 10); + if ((b >= 'A') && (b <= 'F')) return (byte) (b - 'A' + 10); + return 0; + } + +} diff --git a/core/src/main/java/com/glines/socketio/util/JdkOverLog4j.java b/core/src/main/java/com/glines/socketio/util/JdkOverLog4j.java new file mode 100644 index 0000000..07f4e53 --- /dev/null +++ b/core/src/main/java/com/glines/socketio/util/JdkOverLog4j.java @@ -0,0 +1,108 @@ +/** + * The MIT License + * Copyright (c) 2010 Tad Glines + * + * Contributors: Ovea.com, Mycila.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.glines.socketio.util; + +/** + * @author Mathieu Carbou (mathieu.carbou@gmail.com) + */ + +import org.apache.log4j.Level; +import org.apache.log4j.Logger; + +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Handler; +import java.util.logging.LogManager; +import java.util.logging.LogRecord; + +/** + * @author Mathieu Carbou + */ + +/** + * @author Mathieu Carbou + */ +public final class JdkOverLog4j extends Handler { + + private static final Map LEVELS_JDK_TO_LOG4J = new HashMap() { + private static final long serialVersionUID = 4627902405916164098L; + + { + put(java.util.logging.Level.OFF, Level.OFF); + put(java.util.logging.Level.SEVERE, Level.ERROR); + put(java.util.logging.Level.WARNING, Level.WARN); + put(java.util.logging.Level.INFO, Level.INFO); + put(java.util.logging.Level.CONFIG, Level.INFO); + put(java.util.logging.Level.FINE, Level.DEBUG); + put(java.util.logging.Level.FINER, Level.DEBUG); + put(java.util.logging.Level.FINEST, Level.DEBUG); + put(java.util.logging.Level.ALL, Level.ALL); + } + }; + + private static final Map LEVELS_LOG4J_TO_JDK = new HashMap() { + private static final long serialVersionUID = 1880156903738858787L; + + { + put(Level.OFF, java.util.logging.Level.OFF); + put(Level.FATAL, java.util.logging.Level.SEVERE); + put(Level.ERROR, java.util.logging.Level.SEVERE); + put(Level.WARN, java.util.logging.Level.WARNING); + put(Level.INFO, java.util.logging.Level.INFO); + put(Level.DEBUG, java.util.logging.Level.FINE); + put(Level.TRACE, java.util.logging.Level.FINE); + put(Level.ALL, java.util.logging.Level.ALL); + } + }; + + @Override + public void publish(LogRecord record) { + // normalize levels + Logger log4jLogger = Logger.getLogger(record.getLoggerName()); + Level log4jLevel = log4jLogger.getEffectiveLevel(); + java.util.logging.Logger jdkLogger = java.util.logging.Logger.getLogger(record.getLoggerName()); + java.util.logging.Level expectedJdkLevel = LEVELS_LOG4J_TO_JDK.get(log4jLevel); + if (expectedJdkLevel == null) + throw new AssertionError("Level not supported yet - have a bug !" + log4jLevel); + if (!expectedJdkLevel.equals(jdkLogger.getLevel())) { + jdkLogger.setLevel(expectedJdkLevel); + } + log4jLogger.log(record.getLoggerName(), LEVELS_JDK_TO_LOG4J.get(record.getLevel()), record.getMessage(), record.getThrown()); + } + + @Override + public void flush() { + } + + @Override + public void close() throws SecurityException { + } + + public static void install() { + LogManager.getLogManager().reset(); + LogManager.getLogManager().getLogger("").addHandler(new JdkOverLog4j()); + LogManager.getLogManager().getLogger("").setLevel(java.util.logging.Level.ALL); + } +} diff --git a/core/src/main/java/com/glines/socketio/util/Loader.java b/core/src/main/java/com/glines/socketio/util/Loader.java new file mode 100644 index 0000000..03a63e8 --- /dev/null +++ b/core/src/main/java/com/glines/socketio/util/Loader.java @@ -0,0 +1,39 @@ +/** + * The MIT License + * Copyright (c) 2010 Tad Glines + * + * Contributors: Ovea.com, Mycila.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.glines.socketio.util; + +import java.net.URL; +import java.util.List; + +/** + * @author Mathieu Carbou (mathieu.carbou@gmail.com) + */ +public interface Loader { + Class loadClass(String className); + + URL getResource(String path); + + List getResources(String path); +} \ No newline at end of file diff --git a/core/src/main/java/com/glines/socketio/util/ServiceClassLoader.java b/core/src/main/java/com/glines/socketio/util/ServiceClassLoader.java new file mode 100644 index 0000000..f7065df --- /dev/null +++ b/core/src/main/java/com/glines/socketio/util/ServiceClassLoader.java @@ -0,0 +1,207 @@ +/** + * The MIT License + * Copyright (c) 2010 Tad Glines + * + * Contributors: Ovea.com, Mycila.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.glines.socketio.util; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.ServiceConfigurationError; + +/** + * @author Mathieu Carbou + * @param The type of the service to be loaded by this loader + */ +public final class ServiceClassLoader implements Iterable> { + + private static final String PREFIX = "META-INF/services/"; + private final Class service; + private final Loader loader; + private LinkedHashMap> providers = new LinkedHashMap>(); + private LazyIterator lookupIterator; + + public void reload() { + providers.clear(); + lookupIterator = new LazyIterator(service, loader); + } + + private ServiceClassLoader(Class svc, Loader loader) { + this.service = svc; + this.loader = loader; + reload(); + } + + private static void fail(Class service, String msg, Throwable cause) throws ServiceConfigurationError { + throw new ServiceConfigurationError(service.getName() + ": " + msg, cause); + } + + private static void fail(Class service, String msg) throws ServiceConfigurationError { + throw new ServiceConfigurationError(service.getName() + ": " + msg); + } + + private static void fail(Class service, URL u, int line, String msg) throws ServiceConfigurationError { + fail(service, u + ":" + line + ": " + msg); + } + + private int parseLine(Class service, URL u, BufferedReader r, int lc, List names) throws IOException, ServiceConfigurationError { + String ln = r.readLine(); + if (ln == null) return -1; + int ci = ln.indexOf('#'); + if (ci >= 0) ln = ln.substring(0, ci); + ln = ln.trim(); + int n = ln.length(); + if (n != 0) { + if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0)) + fail(service, u, lc, "Illegal configuration-file syntax"); + int cp = ln.codePointAt(0); + if (!Character.isJavaIdentifierStart(cp)) + fail(service, u, lc, "Illegal provider-class name: " + ln); + for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) { + cp = ln.codePointAt(i); + if (!Character.isJavaIdentifierPart(cp) && (cp != '.')) + fail(service, u, lc, "Illegal provider-class name: " + ln); + } + if (!providers.containsKey(ln) && !names.contains(ln)) + names.add(ln); + } + return lc + 1; + } + + private Iterator parse(Class service, URL u) throws ServiceConfigurationError { + InputStream in = null; + BufferedReader r = null; + ArrayList names = new ArrayList(); + try { + in = u.openStream(); + r = new BufferedReader(new InputStreamReader(in, "utf-8")); + int lc = 1; + while ((lc = parseLine(service, u, r, lc, names)) >= 0) ; + } catch (IOException x) { + fail(service, "Error reading configuration file", x); + } finally { + try { + if (r != null) r.close(); + if (in != null) in.close(); + } catch (IOException y) { + fail(service, "Error closing configuration file", y); + } + } + return names.iterator(); + } + + private class LazyIterator implements Iterator> { + final Class service; + final Loader loader; + Iterator configs = null; + Iterator pending = null; + String nextName = null; + + private LazyIterator(Class service, Loader loader) { + this.service = service; + this.loader = loader; + } + + public boolean hasNext() { + if (nextName != null) { + return true; + } + if (configs == null) { + String fullName = PREFIX + service.getName(); + configs = loader.getResources(fullName).iterator(); + } + while ((pending == null) || !pending.hasNext()) { + if (!configs.hasNext()) { + return false; + } + pending = parse(service, configs.next()); + } + nextName = pending.next(); + return true; + } + + @SuppressWarnings({"unchecked"}) + public Class next() { + if (!hasNext()) + throw new NoSuchElementException(); + String cn = nextName; + nextName = null; + try { + Class p = (Class) loader.loadClass(cn); + providers.put(cn, p); + return p; + } catch (RuntimeException x) { + fail(service, + "Provider " + cn + " could not be instantiated: " + x, + x); + } + throw new Error(); // This cannot happen + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + } + + public Iterator> iterator() { + return new Iterator>() { + Iterator>> knownProviders = providers.entrySet().iterator(); + + public boolean hasNext() { + return knownProviders.hasNext() || lookupIterator.hasNext(); + } + + public Class next() { + if (knownProviders.hasNext()) + return knownProviders.next().getValue(); + return lookupIterator.next(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + public static ServiceClassLoader load(Class service, Loader loader) { + return new ServiceClassLoader(service, loader); + } + + public static ServiceClassLoader load(Class service) { + return new ServiceClassLoader(service, new DefaultLoader()); + } + + public String toString() { + return "ServiceClassLoader[" + service.getName() + "]"; + } + +} \ No newline at end of file diff --git a/core/src/main/java/com/glines/socketio/util/URI.java b/core/src/main/java/com/glines/socketio/util/URI.java new file mode 100644 index 0000000..58da700 --- /dev/null +++ b/core/src/main/java/com/glines/socketio/util/URI.java @@ -0,0 +1,122 @@ +/** + * The MIT License + * Copyright (c) 2010 Tad Glines + * + * Contributors: Ovea.com, Mycila.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.glines.socketio.util; + +import java.io.UnsupportedEncodingException; + +public final class URI { + + private URI() { + } + + public static String decodePath(String path) { + return decodePath(path, "UTF-8"); + } + + /* Decode a URI path. + * @param path The path the encode + * @param buf StringBuilder to encode path into + */ + public static String decodePath(String path, String charset) { + if (path == null) + return null; + char[] chars = null; + int n = 0; + byte[] bytes = null; + int b = 0; + + int len = path.length(); + + for (int i = 0; i < len; i++) { + char c = path.charAt(i); + + if (c == '%' && (i + 2) < len) { + if (chars == null) { + chars = new char[len]; + bytes = new byte[len]; + path.getChars(0, i, chars, 0); + } + bytes[b++] = (byte) (0xff & parseInt(path, i + 1, 2, 16)); + i += 2; + continue; + } else if (bytes == null) { + n++; + continue; + } + + if (b > 0) { + String s; + try { + s = new String(bytes, 0, b, charset); + } catch (UnsupportedEncodingException e) { + s = new String(bytes, 0, b); + } + s.getChars(0, s.length(), chars, n); + n += s.length(); + b = 0; + } + + chars[n++] = c; + } + + if (chars == null) + return path; + + if (b > 0) { + String s; + try { + s = new String(bytes, 0, b, charset); + } catch (UnsupportedEncodingException e) { + s = new String(bytes, 0, b); + } + s.getChars(0, s.length(), chars, n); + n += s.length(); + } + + return new String(chars, 0, n); + } + + private static int parseInt(String s, int offset, int length, int base) throws NumberFormatException { + int value = 0; + if (length < 0) + length = s.length() - offset; + for (int i = 0; i < length; i++) { + char c = s.charAt(offset + i); + int digit = c - '0'; + if (digit < 0 || digit >= base || digit >= 10) { + digit = 10 + c - 'A'; + if (digit < 10 || digit >= base) + digit = 10 + c - 'a'; + } + if (digit < 0 || digit >= base) + throw new NumberFormatException(s.substring(offset, offset + length)); + value = value * base + digit; + } + return value; + } +} + + + diff --git a/core/src/main/java/com/glines/socketio/util/Web.java b/core/src/main/java/com/glines/socketio/util/Web.java new file mode 100644 index 0000000..660fe06 --- /dev/null +++ b/core/src/main/java/com/glines/socketio/util/Web.java @@ -0,0 +1,64 @@ +/** + * The MIT License + * Copyright (c) 2010 Tad Glines + * + * Contributors: Ovea.com, Mycila.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.glines.socketio.util; + +import javax.servlet.http.HttpServletRequest; +import java.security.SecureRandom; +import java.util.Random; + +/** + * @author Mathieu Carbou + */ +public final class Web { + + private static final char[] BASE64_ALPHABET ="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".toCharArray(); + private static Random random = new SecureRandom(); + + private Web() { + } + + public static String extractSessionId(HttpServletRequest request) { + String path = request.getPathInfo(); + if (path != null && path.length() > 0 && !"/".equals(path)) { + if (path.startsWith("/")) path = path.substring(1); + String[] parts = path.split("/"); + if (parts.length >= 2) { + return parts[1] == null || parts[1].length() == 0 || parts[1].equals("null") ? null : parts[1]; + } + } + return null; + } + + public static String generateRandomString(int length) { + StringBuilder result = new StringBuilder(length); + byte[] bytes = new byte[length]; + random.nextBytes(bytes); + for (byte aByte : bytes) { + result.append(BASE64_ALPHABET[aByte & 0x3F]); + } + return result.toString(); + } + +} diff --git a/core/src/main/resources/META-INF/services/com.glines.socketio.server.Transport b/core/src/main/resources/META-INF/services/com.glines.socketio.server.Transport new file mode 100644 index 0000000..aa6896e --- /dev/null +++ b/core/src/main/resources/META-INF/services/com.glines.socketio.server.Transport @@ -0,0 +1,5 @@ +com.glines.socketio.server.transport.FlashSocketTransport +com.glines.socketio.server.transport.HTMLFileTransport +com.glines.socketio.server.transport.XHRMultipartTransport +com.glines.socketio.server.transport.XHRPollingTransport +com.glines.socketio.server.transport.JSONPPollingTransport diff --git a/core/src/main/resources/com/glines/socketio/socket.io.js b/core/src/main/resources/com/glines/socketio/socket.io.js index ff26812..c71ce83 100644 --- a/core/src/main/resources/com/glines/socketio/socket.io.js +++ b/core/src/main/resources/com/glines/socketio/socket.io.js @@ -313,7 +313,7 @@ if (typeof window != 'undefined'){ + ':' + this.base.options.port + '/' + this.base.options.resource + '/' + this.type - + (this.sessionid ? ('/' + this.sessionid) : '/'); + + '/' + (this.sessionid ? this.sessionid : 'null'); }; })(); @@ -548,7 +548,7 @@ if (typeof window != 'undefined'){ + ':' + this.base.options.port + '/' + this.base.options.resource + '/' + this.type - + (this.sessionid ? ('/' + this.sessionid) : ''); + + '/' + (this.sessionid ? this.sessionid : 'null'); }; WS.check = function(){ diff --git a/extension/gwt/src/main/java/com/glines/socketio/client/gwt/GWTSocketIOConnectionFactory.java b/extension/gwt/src/main/java/com/glines/socketio/client/gwt/GWTSocketIOConnectionFactory.java index 9e4d329..9d04222 100644 --- a/extension/gwt/src/main/java/com/glines/socketio/client/gwt/GWTSocketIOConnectionFactory.java +++ b/extension/gwt/src/main/java/com/glines/socketio/client/gwt/GWTSocketIOConnectionFactory.java @@ -2,6 +2,8 @@ * The MIT License * Copyright (c) 2010 Tad Glines * + * Contributors: Ovea.com, Mycila.com + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights @@ -23,6 +25,7 @@ package com.glines.socketio.client.gwt; import com.glines.socketio.client.common.SocketIOConnection; +import com.glines.socketio.client.common.SocketIOConnectionListener; public class GWTSocketIOConnectionFactory implements SocketIOConnection.Factory { public static SocketIOConnection.Factory INSTANCE = new GWTSocketIOConnectionFactory(); @@ -33,7 +36,7 @@ public class GWTSocketIOConnectionFactory implements SocketIOConnection.Factory * @param port * @return */ - public SocketIOConnection create(SocketIOConnection.SocketIOConnectionListener listener, + public SocketIOConnection create(SocketIOConnectionListener listener, String host, short port) { return new GWTSocketIOConnectionImpl(listener, host, port); } diff --git a/extension/gwt/src/main/java/com/glines/socketio/client/gwt/GWTSocketIOConnectionImpl.java b/extension/gwt/src/main/java/com/glines/socketio/client/gwt/GWTSocketIOConnectionImpl.java index dc8d90d..3a1388f 100644 --- a/extension/gwt/src/main/java/com/glines/socketio/client/gwt/GWTSocketIOConnectionImpl.java +++ b/extension/gwt/src/main/java/com/glines/socketio/client/gwt/GWTSocketIOConnectionImpl.java @@ -2,6 +2,8 @@ * The MIT License * Copyright (c) 2010 Tad Glines * + * Contributors: Ovea.com, Mycila.com + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights @@ -23,6 +25,7 @@ package com.glines.socketio.client.gwt; import com.glines.socketio.client.common.SocketIOConnection; +import com.glines.socketio.client.common.SocketIOConnectionListener; import com.glines.socketio.common.ConnectionState; import com.glines.socketio.common.DisconnectReason; import com.glines.socketio.common.SocketIOException; @@ -59,12 +62,12 @@ protected SocketIOImpl() { } - private final SocketIOConnection.SocketIOConnectionListener listener; + private final SocketIOConnectionListener listener; private final String host; private final String port; private SocketIOImpl socket = null; - GWTSocketIOConnectionImpl(SocketIOConnection.SocketIOConnectionListener listener, + GWTSocketIOConnectionImpl(SocketIOConnectionListener listener, String host, short port) { this.listener = listener; this.host = host; diff --git a/extension/gwt/src/main/resources/com/glines/socketio/Common.gwt.xml b/extension/gwt/src/main/resources/com/glines/socketio/Common.gwt.xml index 2e83574..97a148d 100644 --- a/extension/gwt/src/main/resources/com/glines/socketio/Common.gwt.xml +++ b/extension/gwt/src/main/resources/com/glines/socketio/Common.gwt.xml @@ -3,6 +3,8 @@ The MIT License Copyright (c) 2010 Tad Glines + Contributors: Ovea.com, Mycila.com + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights diff --git a/extension/gwt/src/main/resources/com/glines/socketio/client/Client.gwt.xml b/extension/gwt/src/main/resources/com/glines/socketio/client/Client.gwt.xml index b151ca2..5a6be10 100644 --- a/extension/gwt/src/main/resources/com/glines/socketio/client/Client.gwt.xml +++ b/extension/gwt/src/main/resources/com/glines/socketio/client/Client.gwt.xml @@ -3,6 +3,8 @@ The MIT License Copyright (c) 2010 Tad Glines + Contributors: Ovea.com, Mycila.com + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights diff --git a/extension/jetty/pom.xml b/extension/jetty/pom.xml new file mode 100644 index 0000000..9fcbb73 --- /dev/null +++ b/extension/jetty/pom.xml @@ -0,0 +1,49 @@ + + + + 4.0.0 + + + com.glines.socketio.extension + socketio-extension + 0.1-SNAPSHOT + .. + + + socketio-jetty + jar + + Socket.IO Extension Jetty + Socket.IO Java Jetty Extension + + + + com.glines.socketio + socketio-core + + + + org.mortbay.jetty + servlet-api + provided + + + + org.eclipse.jetty + jetty-server + provided + + + org.eclipse.jetty + jetty-continuation + provided + + + org.eclipse.jetty + jetty-websocket + provided + + + + diff --git a/extension/jetty/src/main/java/com/glines/socketio/server/transport/jetty/ConnectionTimeoutPreventer.java b/extension/jetty/src/main/java/com/glines/socketio/server/transport/jetty/ConnectionTimeoutPreventer.java new file mode 100644 index 0000000..8cf4ef6 --- /dev/null +++ b/extension/jetty/src/main/java/com/glines/socketio/server/transport/jetty/ConnectionTimeoutPreventer.java @@ -0,0 +1,32 @@ +/** + * The MIT License + * Copyright (c) 2010 Tad Glines + * + * Contributors: Ovea.com, Mycila.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.glines.socketio.server.transport.jetty; + +/** +* @author Mathieu Carbou +*/ +interface ConnectionTimeoutPreventer { + void connectionActive(); +} diff --git a/extension/jetty/src/main/java/com/glines/socketio/server/transport/jetty/ConnectionTimeoutPreventerDataHandler.java b/extension/jetty/src/main/java/com/glines/socketio/server/transport/jetty/ConnectionTimeoutPreventerDataHandler.java new file mode 100644 index 0000000..44f2e3f --- /dev/null +++ b/extension/jetty/src/main/java/com/glines/socketio/server/transport/jetty/ConnectionTimeoutPreventerDataHandler.java @@ -0,0 +1,84 @@ +/** + * The MIT License + * Copyright (c) 2010 Tad Glines + * + * Contributors: Ovea.com, Mycila.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.glines.socketio.server.transport.jetty; + +import com.glines.socketio.server.SocketIOConfig; +import com.glines.socketio.server.transport.DataHandler; + +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * @author Mathieu Carbou + */ +final class ConnectionTimeoutPreventerDataHandler implements DataHandler { + + private final DataHandler delegate; + private final ConnectionTimeoutPreventer timeoutPreventer; + + public ConnectionTimeoutPreventerDataHandler(DataHandler delegate, ConnectionTimeoutPreventer timeoutPreventer) { + this.delegate = delegate; + this.timeoutPreventer = timeoutPreventer; + } + + @Override + public void init(SocketIOConfig config) { + delegate.init(config); + } + + @Override + public boolean isConnectionPersistent() { + return delegate.isConnectionPersistent(); + } + + @Override + public void onConnect(HttpServletRequest request, HttpServletResponse response) throws IOException { + timeoutPreventer.connectionActive(); + delegate.onConnect(request, response); + } + + @Override + public void onFinishSend(ServletResponse response) throws IOException { + delegate.onFinishSend(response); + } + + @Override + public void onStartSend(HttpServletResponse response) throws IOException { + delegate.onStartSend(response); + } + + @Override + public void onWriteData(ServletResponse response, String data) throws IOException { + timeoutPreventer.connectionActive(); + delegate.onWriteData(response, data); + } + + @Override + public String toString() { + return "withTimeoutPreventer(" + delegate + ")"; + } +} diff --git a/extension/jetty/src/main/java/com/glines/socketio/server/transport/jetty/JettyContinuationTransportHandler.java b/extension/jetty/src/main/java/com/glines/socketio/server/transport/jetty/JettyContinuationTransportHandler.java new file mode 100644 index 0000000..0832da9 --- /dev/null +++ b/extension/jetty/src/main/java/com/glines/socketio/server/transport/jetty/JettyContinuationTransportHandler.java @@ -0,0 +1,447 @@ +/** + * The MIT License + * Copyright (c) 2010 Tad Glines + * + * Contributors: Ovea.com, Mycila.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.glines.socketio.server.transport.jetty; + +import com.glines.socketio.annotation.Handle; +import com.glines.socketio.common.ConnectionState; +import com.glines.socketio.common.DisconnectReason; +import com.glines.socketio.common.SocketIOException; +import com.glines.socketio.server.*; +import com.glines.socketio.server.transport.AbstractHttpTransport; +import com.glines.socketio.server.transport.DataHandler; +import com.glines.socketio.util.IO; +import com.glines.socketio.util.URI; +import org.eclipse.jetty.continuation.Continuation; +import org.eclipse.jetty.continuation.ContinuationListener; +import org.eclipse.jetty.continuation.ContinuationSupport; +import org.eclipse.jetty.server.HttpConnection; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.BufferedReader; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Mathieu Carbou + */ +@Handle({TransportType.HTML_FILE, TransportType.JSONP_POLLING, TransportType.XHR_MULTIPART, TransportType.XHR_POLLING}) +public final class JettyContinuationTransportHandler extends AbstractTransportHandler implements ContinuationListener, ConnectableTransportHandler { + + /** + * This specifies how long to wait for a pong (ping response). + */ + private static final long DEFAULT_HEARTBEAT_TIMEOUT = 10 * 1000; + + /** + * The amount of time the session will wait before trying to send a ping. + * Using a value of half the HTTP_REQUEST_TIMEOUT should be good enough. + */ + private static final long DEFAULT_HEARTBEAT_DELAY = 15 * 1000; + + /** + * For non persistent connection transports, this is the amount of time to wait + * for messages before returning empty results. + */ + private static final long DEFAULT_TIMEOUT = 5 * 1000; + + /** + * For non persistent connection transports, this is the amount of time to wait + * for messages before returning empty results. + */ + private static final long DEFAULT_CONTINUATION_TIMEOUT = 20 * 1000; + + private static final String CONTINUATION_KEY = JettyContinuationTransportHandler.class.getName() + ".Continuation"; + private static final Logger LOGGER = Logger.getLogger(JettyContinuationTransportHandler.class.getName()); + + private volatile boolean is_open; + private volatile Continuation continuation; + private volatile boolean disconnectWhenEmpty; + + private TransportBuffer buffer; + private int bufferSize; + private int maxIdleTime; + private DataHandler dataHandler; + private long continuationTimeout; + + @Override + public void setDataHandler(DataHandler dataHandler) { + this.dataHandler = dataHandler.isConnectionPersistent() ? + new ConnectionTimeoutPreventerDataHandler(dataHandler, newTimeoutPreventor()) : + dataHandler; + } + + @Override + protected final void init() { + this.bufferSize = getConfig().getBufferSize(); + this.maxIdleTime = getConfig().getMaxIdle(); + this.buffer = new TransportBuffer(bufferSize); + if (dataHandler.isConnectionPersistent()) { + getSession().setHeartbeat(getConfig().getHeartbeatDelay(DEFAULT_HEARTBEAT_DELAY)); + getSession().setTimeout(getConfig().getHeartbeatTimeout(DEFAULT_HEARTBEAT_TIMEOUT)); + if (LOGGER.isLoggable(Level.FINE)) + LOGGER.fine(getConfig().getNamespace() + " transport handler configuration:\n" + + " - heartbeatDelay=" + getSession().getHeartbeat() + "\n" + + " - heartbeatTimeout=" + getSession().getTimeout()); + } else { + getSession().setTimeout(getConfig().getTimeout(DEFAULT_TIMEOUT)); + this.continuationTimeout = getConfig().getLong("continuationTimeout", DEFAULT_CONTINUATION_TIMEOUT); + if (LOGGER.isLoggable(Level.FINE)) + LOGGER.fine(getConfig().getNamespace() + " transport handler configuration:\n" + + " - timeout=" + getSession().getTimeout() + "\n" + + " - continuationTimeout=" + this.continuationTimeout); + } + } + + @Override + public void disconnect() { + getSession().onDisconnect(DisconnectReason.DISCONNECT); + abort(); + } + + @Override + public void close() { + getSession().startClose(); + } + + @Override + public ConnectionState getConnectionState() { + return getSession().getConnectionState(); + } + + @Override + public void sendMessage(SocketIOFrame frame) throws SocketIOException { + if (LOGGER.isLoggable(Level.FINE)) + LOGGER.log(Level.FINE, "Session[" + getSession().getSessionId() + "]: " + "sendMessage(frame): [" + frame.getFrameType() + "]: " + frame.getData()); + if (is_open) { + if (continuation != null) { + List messages = buffer.drainMessages(); + messages.add(frame.encode()); + StringBuilder data = new StringBuilder(); + for (String msg : messages) { + data.append(msg); + } + try { + dataHandler.onWriteData(continuation.getServletResponse(), data.toString()); + } catch (IOException e) { + throw new SocketIOException(e); + } + if (!dataHandler.isConnectionPersistent() && !continuation.isInitial()) { + Continuation cont = continuation; + continuation = null; + cont.complete(); + } else { + getSession().startHeartbeatTimer(); + } + } else { + String data = frame.encode(); + if (!buffer.putMessage(data, maxIdleTime)) { + getSession().onDisconnect(DisconnectReason.TIMEOUT); + abort(); + throw new SocketIOException(); + } + } + } else { + throw new SocketIOClosedException(); + } + } + + @Override + public void sendMessage(String message) throws SocketIOException { + if (LOGGER.isLoggable(Level.FINE)) + LOGGER.log(Level.FINE, "Session[" + getSession().getSessionId() + "]: " + "sendMessage(String): " + message); + sendMessage(SocketIOFrame.TEXT_MESSAGE_TYPE, message); + } + + @Override + public void sendMessage(int messageType, String message) throws SocketIOException { + if (LOGGER.isLoggable(Level.FINE)) + LOGGER.log(Level.FINE, "Session[" + getSession().getSessionId() + "]: " + "sendMessage(int, String): [" + messageType + "]: " + message); + if (is_open && getSession().getConnectionState() == ConnectionState.CONNECTED) { + sendMessage(new SocketIOFrame(SocketIOFrame.FrameType.DATA, messageType, message)); + } else { + throw new SocketIOClosedException(); + } + } + + @Override + public void handle(HttpServletRequest request, HttpServletResponse response, SocketIOSession session) throws IOException { + if ("GET".equals(request.getMethod())) { + if (!is_open && buffer.isEmpty()) { + response.sendError(HttpServletResponse.SC_NOT_FOUND); + } else { + Continuation cont = (Continuation) request.getAttribute(CONTINUATION_KEY); + if (continuation != null || cont != null) { + if (continuation == cont) { + continuation = null; + dataHandler.onFinishSend(response); + } + if (cont != null) { + request.removeAttribute(CONTINUATION_KEY); + } + return; + } + if (!dataHandler.isConnectionPersistent()) { + if (!buffer.isEmpty()) { + List messages = buffer.drainMessages(); + if (messages.size() > 0) { + StringBuilder data = new StringBuilder(); + for (String msg : messages) { + data.append(msg); + } + dataHandler.onStartSend(response); + dataHandler.onWriteData(response, data.toString()); + dataHandler.onFinishSend(response); + if (!disconnectWhenEmpty) { + getSession().startTimeoutTimer(); + } else { + abort(); + } + } + } else { + getSession().clearTimeoutTimer(); + request.setAttribute(AbstractHttpTransport.SESSION_KEY, session); + response.setBufferSize(bufferSize); + continuation = ContinuationSupport.getContinuation(request); + continuation.addContinuationListener(this); + continuation.setTimeout(continuationTimeout); + continuation.suspend(response); + request.setAttribute(CONTINUATION_KEY, continuation); + dataHandler.onStartSend(response); + } + } else { + response.sendError(HttpServletResponse.SC_NOT_FOUND); + } + } + } else if ("POST".equals(request.getMethod())) { + if (is_open) { + int size = request.getContentLength(); + BufferedReader reader = request.getReader(); + if (size == 0) { + response.sendError(HttpServletResponse.SC_BAD_REQUEST); + } else { + String data = decodePostData(request.getContentType(), IO.toString(reader)); + if (data != null && data.length() > 0) { + List list = SocketIOFrame.parse(data); + for (SocketIOFrame msg : list) { + getSession().onMessage(msg); + } + } + // Ensure that the disconnectWhenEmpty flag is obeyed in the case where + // it is set during a POST. + if (disconnectWhenEmpty && buffer.isEmpty()) { + if (getSession().getConnectionState() == ConnectionState.CLOSING) { + getSession().onDisconnect(DisconnectReason.CLOSED); + } + abort(); + } + } + } + } else { + response.sendError(HttpServletResponse.SC_BAD_REQUEST); + } + + } + + protected String decodePostData(String contentType, String data) { + if (contentType.startsWith("application/x-www-form-urlencoded")) { + if (data.substring(0, 5).equals("data=")) { + return URI.decodePath(data.substring(5)); + } else { + return ""; + } + } else if (contentType.startsWith("text/plain")) { + return data; + } else { + // TODO: Treat as text for now, maybe error in the future. + return data; + } + } + + @Override + public void onComplete(Continuation cont) { + if (continuation != null && cont == continuation) { + continuation = null; + if (dataHandler.isConnectionPersistent()) { + is_open = false; + if (!disconnectWhenEmpty) { + getSession().onDisconnect(DisconnectReason.DISCONNECT); + } + abort(); + } else { + if (!is_open && buffer.isEmpty() && !disconnectWhenEmpty) { + getSession().onDisconnect(DisconnectReason.DISCONNECT); + abort(); + } else { + if (disconnectWhenEmpty) { + abort(); + } else { + getSession().startTimeoutTimer(); + } + } + } + } + } + + @Override + public void onTimeout(Continuation cont) { + if (continuation != null && cont == continuation) { + continuation = null; + if (dataHandler.isConnectionPersistent()) { + is_open = false; + getSession().onDisconnect(DisconnectReason.TIMEOUT); + abort(); + } else { + if (!is_open && buffer.isEmpty()) { + getSession().onDisconnect(DisconnectReason.DISCONNECT); + abort(); + } else { + try { + dataHandler.onFinishSend(cont.getServletResponse()); + } catch (IOException e) { + getSession().onDisconnect(DisconnectReason.DISCONNECT); + abort(); + } + } + getSession().startTimeoutTimer(); + } + } + } + + @Override + public void connect(HttpServletRequest request, HttpServletResponse response) throws IOException { + request.setAttribute(AbstractHttpTransport.SESSION_KEY, getSession()); + response.setBufferSize(bufferSize); + continuation = ContinuationSupport.getContinuation(request); + continuation.addContinuationListener(this); + if (dataHandler.isConnectionPersistent()) { + continuation.setTimeout(0); + } + dataHandler.onConnect(request, response); + is_open = true; + getSession().onConnect(this); + dataHandler.onFinishSend(response); + if (continuation != null) { + if (dataHandler.isConnectionPersistent()) { + request.setAttribute(CONTINUATION_KEY, continuation); + continuation.suspend(response); + } else { + continuation = null; + } + } + } + + @Override + public void disconnectWhenEmpty() { + disconnectWhenEmpty = true; + } + + @Override + public void abort() { + getSession().clearHeartbeatTimer(); + getSession().clearTimeoutTimer(); + is_open = false; + if (continuation != null) { + Continuation cont = continuation; + continuation = null; + if (cont.isSuspended()) { + cont.complete(); + } + } + buffer.setListener(new TransportBuffer.BufferListener() { + @Override + public boolean onMessages(List messages) { + return false; + } + + @Override + public boolean onMessage(String message) { + return false; + } + }); + buffer.clear(); + getSession().onShutdown(); + } + + /** + * This must be called within the context of an active HTTP request. + */ + private static ConnectionTimeoutPreventer newTimeoutPreventor() { + HttpConnection httpConnection = HttpConnection.getCurrentConnection(); + if (httpConnection == null) { + LOGGER.log(Level.FINE, "No HttpConnection boundto local thread: " + Thread.currentThread().getName()); + return new ConnectionTimeoutPreventer() { + @Override + public void connectionActive() { + } + }; + } else { + //call code reflectively because by default we have no access to jetty internal classes from a webapp + // thus by only using HttpConnection we only need to add "-org.eclipse.jetty.server.HttpConnection" to server classes + // to allow access to this class from a webapp + final Object endpoint = httpConnection.getEndPoint(); + // try to cancel IDLE time + try { + LOGGER.fine("TimeoutPreventor - Invoking cancelIdle() method on endpoint class " + endpoint.getClass().getName()); + Method cancelIdle = endpoint.getClass().getMethod("cancelIdle"); + cancelIdle.invoke(endpoint); + } catch (NoSuchMethodException e) { + LOGGER.fine("TimeoutPreventor - No cancelIdle() method on endpoint class " + endpoint.getClass().getName()); + } catch (IllegalAccessException e) { + LOGGER.warning("TimeoutPreventor - Cannot access cancelIdle() method on endpoint class " + endpoint.getClass().getName()); + } catch (InvocationTargetException e) { + LOGGER.log(Level.WARNING, "TimeoutPreventor - Error calling cancelIdle() method on endpoint class " + endpoint.getClass().getName() + ": " + e.getMessage(), e); + } + // try to find scheduleIdle() method + try { + final Method scheduleIdle = endpoint.getClass().getMethod("scheduleIdle"); + return new ConnectionTimeoutPreventer() { + @Override + public void connectionActive() { + try { + LOGGER.fine("TimeoutPreventor - Invoking scheduleIdle() method on endpoint class " + endpoint.getClass().getName()); + scheduleIdle.invoke(endpoint); + } catch (IllegalAccessException e) { + LOGGER.warning("TimeoutPreventor - Cannot access scheduleIdle() method on endpoint class " + endpoint.getClass().getName()); + } catch (InvocationTargetException e) { + LOGGER.log(Level.WARNING, "TimeoutPreventor - Error calling scheduleIdle() method on endpoint class " + endpoint.getClass().getName() + ": " + e.getMessage(), e); + } + } + }; + } catch (NoSuchMethodException e) { + // if the method does not exit, do nothing + return new ConnectionTimeoutPreventer() { + @Override + public void connectionActive() { + } + }; + } + } + } +} diff --git a/extension/jetty/src/main/java/com/glines/socketio/server/transport/jetty/JettyWebSocketTransport.java b/extension/jetty/src/main/java/com/glines/socketio/server/transport/jetty/JettyWebSocketTransport.java new file mode 100644 index 0000000..6c17cae --- /dev/null +++ b/extension/jetty/src/main/java/com/glines/socketio/server/transport/jetty/JettyWebSocketTransport.java @@ -0,0 +1,117 @@ +/** + * The MIT License + * Copyright (c) 2010 Tad Glines + * + * Contributors: Ovea.com, Mycila.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.glines.socketio.server.transport.jetty; + +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.websocket.WebSocket; +import org.eclipse.jetty.websocket.WebSocketFactory; +import org.eclipse.jetty.websocket.WebSocketFactory.Acceptor; + +import com.glines.socketio.server.AbstractTransport; +import com.glines.socketio.server.SessionManager; +import com.glines.socketio.server.SocketIOInbound; +import com.glines.socketio.server.SocketIOSession; +import com.glines.socketio.server.Transport; +import com.glines.socketio.server.TransportHandler; +import com.glines.socketio.server.TransportInitializationException; +import com.glines.socketio.server.TransportType; +import com.glines.socketio.util.Web; + +public final class JettyWebSocketTransport extends AbstractTransport { + + private static final Logger LOGGER = Logger.getLogger(JettyWebSocketTransport.class.getName()); + + private final WebSocketFactory wsFactory = new WebSocketFactory(new Acceptor() { + @Override + public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean checkOrigin(HttpServletRequest arg0, String arg1) { + throw new UnsupportedOperationException(); + } + }); + + @Override + public void init() throws TransportInitializationException { + wsFactory.setBufferSize(getConfig().getBufferSize()); + wsFactory.setMaxIdleTime(getConfig().getMaxIdle()); + if (LOGGER.isLoggable(Level.FINE)) + LOGGER.fine(getType() + " configuration:\n" + + " - bufferSize=" + wsFactory.getBufferSize() + "\n" + + " - maxIdle=" + wsFactory.getMaxIdleTime()); + } + + @Override + public TransportType getType() { + return TransportType.WEB_SOCKET; + } + + @Override + public void handle(HttpServletRequest request, + HttpServletResponse response, + Transport.InboundFactory inboundFactory, + SessionManager sessionFactory) throws IOException { + + String sessionId = Web.extractSessionId(request); + + if ("GET".equals(request.getMethod()) && sessionId == null && "WebSocket".equals(request.getHeader("Upgrade"))) { + boolean hixie = request.getHeader("Sec-WebSocket-Key1") != null; + + String protocol = request.getHeader(hixie ? "Sec-WebSocket-Protocol" : "WebSocket-Protocol"); + if (protocol == null) + protocol = request.getHeader("Sec-WebSocket-Protocol"); + + String host = request.getHeader("Host"); + String origin = request.getHeader("Origin"); + if (origin == null) { + origin = host; + } + + SocketIOInbound inbound = inboundFactory.getInbound(request); + if (inbound == null) { + if (hixie) { + response.setHeader("Connection", "close"); + } + response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE); + } else { + SocketIOSession session = sessionFactory.createSession(inbound); + TransportHandler handler = newHandler(WebSocket.class, session); + handler.init(getConfig()); + wsFactory.upgrade(request, response, WebSocket.class.cast(handler), protocol); + } + } else { + response.sendError(HttpServletResponse.SC_BAD_REQUEST, this + " transport error: Invalid request"); + } + } + +} diff --git a/extension/jetty/src/main/java/com/glines/socketio/server/transport/jetty/JettyWebSocketTransportHandler.java b/extension/jetty/src/main/java/com/glines/socketio/server/transport/jetty/JettyWebSocketTransportHandler.java new file mode 100644 index 0000000..e802777 --- /dev/null +++ b/extension/jetty/src/main/java/com/glines/socketio/server/transport/jetty/JettyWebSocketTransportHandler.java @@ -0,0 +1,169 @@ +/** + * The MIT License + * Copyright (c) 2010 Tad Glines + * + * Contributors: Ovea.com, Mycila.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.glines.socketio.server.transport.jetty; + +import com.glines.socketio.annotation.Handle; +import com.glines.socketio.common.ConnectionState; +import com.glines.socketio.common.DisconnectReason; +import com.glines.socketio.common.SocketIOException; +import com.glines.socketio.server.*; +import org.eclipse.jetty.websocket.WebSocket; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Mathieu Carbou + */ +@Handle({TransportType.WEB_SOCKET, TransportType.FLASH_SOCKET}) +public final class JettyWebSocketTransportHandler extends AbstractTransportHandler implements WebSocket.OnTextMessage, WebSocket.OnBinaryMessage { + + private static final long DEFAULT_HEARTBEAT_DELAY = SocketIOConfig.DEFAULT_MAX_IDLE / 2; + private static final long DEFAULT_HEARTBEAT_TIMEOUT = 10 * 1000; + + private static final Logger LOGGER = Logger.getLogger(JettyWebSocketTransportHandler.class.getName()); + + private Connection outbound; + private boolean initiated; + + @Override + protected void init() { + getSession().setHeartbeat(getConfig().getHeartbeatDelay(DEFAULT_HEARTBEAT_DELAY)); + getSession().setTimeout(getConfig().getHeartbeatTimeout(DEFAULT_HEARTBEAT_TIMEOUT)); + if (LOGGER.isLoggable(Level.FINE)) + LOGGER.fine(getConfig().getNamespace() + " transport handler configuration:\n" + + " - heartbeatDelay=" + getSession().getHeartbeat() + "\n" + + " - heartbeatTimeout=" + getSession().getTimeout()); + } + + @Override + public void onOpen(Connection connection) { + this.outbound = connection; + } + + @Override + public void onClose(int closeCode, String message) { + getSession().onShutdown(); + } + + @Override + public void onMessage(String message) { + getSession().startHeartbeatTimer(); + if (!initiated) { + if ("OPEN".equals(message)) { + try { + outbound.sendMessage(SocketIOFrame.encode(SocketIOFrame.FrameType.SESSION_ID, 0, getSession().getSessionId())); + outbound.sendMessage(SocketIOFrame.encode(SocketIOFrame.FrameType.HEARTBEAT_INTERVAL, 0, "" + getSession().getHeartbeat())); + getSession().onConnect(this); + initiated = true; + } catch (IOException e) { + outbound.disconnect(); + getSession().onShutdown(); + } + } else { + outbound.disconnect(); + getSession().onShutdown(); + } + } else { + List messages = SocketIOFrame.parse(message); + for (SocketIOFrame msg : messages) { + getSession().onMessage(msg); + } + } + } + + @Override + public void onMessage(byte[] data, int offset, int length) { + try { + onMessage(new String(data, offset, length, "UTF-8")); + } catch (UnsupportedEncodingException e) { + // Do nothing for now. + } + } + + @Override + public void disconnect() { + getSession().onDisconnect(DisconnectReason.DISCONNECT); + outbound.disconnect(); + } + + @Override + public void close() { + getSession().startClose(); + } + + @Override + public ConnectionState getConnectionState() { + return getSession().getConnectionState(); + } + + @Override + public void sendMessage(SocketIOFrame frame) throws SocketIOException { + if (outbound.isOpen()) { + if (LOGGER.isLoggable(Level.FINE)) + LOGGER.log(Level.FINE, "Session[" + getSession().getSessionId() + "]: sendMessage: [" + frame.getFrameType() + "]: " + frame.getData()); + try { + outbound.sendMessage(frame.encode()); + } catch (IOException e) { + outbound.disconnect(); + throw new SocketIOException(e); + } + } else { + throw new SocketIOClosedException(); + } + } + + @Override + public void sendMessage(String message) throws SocketIOException { + sendMessage(SocketIOFrame.TEXT_MESSAGE_TYPE, message); + } + + @Override + public void sendMessage(int messageType, String message) + throws SocketIOException { + if (outbound.isOpen() && getSession().getConnectionState() == ConnectionState.CONNECTED) { + sendMessage(new SocketIOFrame(SocketIOFrame.FrameType.DATA, messageType, message)); + } else { + throw new SocketIOClosedException(); + } + } + + @Override + public void handle(HttpServletRequest request, HttpServletResponse response, SocketIOSession session) throws IOException { + response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Unexpected request on upgraded WebSocket connection"); + } + + @Override + public void abort() { + outbound.disconnect(); + outbound = null; + getSession().onShutdown(); + } +} diff --git a/core/src/main/java/com/glines/socketio/server/transport/TransportBuffer.java b/extension/jetty/src/main/java/com/glines/socketio/server/transport/jetty/TransportBuffer.java similarity index 96% rename from core/src/main/java/com/glines/socketio/server/transport/TransportBuffer.java rename to extension/jetty/src/main/java/com/glines/socketio/server/transport/jetty/TransportBuffer.java index 8008fd7..c433b5e 100644 --- a/core/src/main/java/com/glines/socketio/server/transport/TransportBuffer.java +++ b/extension/jetty/src/main/java/com/glines/socketio/server/transport/jetty/TransportBuffer.java @@ -2,6 +2,8 @@ * The MIT License * Copyright (c) 2010 Tad Glines * + * Contributors: Ovea.com, Mycila.com + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights @@ -20,7 +22,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.glines.socketio.server.transport; +package com.glines.socketio.server.transport.jetty; import java.util.ArrayList; import java.util.List; @@ -30,7 +32,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; -public class TransportBuffer { +final class TransportBuffer { public interface BufferListener { /** * @param message diff --git a/extension/jetty/src/main/resources/META-INF/services/com.glines.socketio.server.Transport b/extension/jetty/src/main/resources/META-INF/services/com.glines.socketio.server.Transport new file mode 100644 index 0000000..149f092 --- /dev/null +++ b/extension/jetty/src/main/resources/META-INF/services/com.glines.socketio.server.Transport @@ -0,0 +1 @@ +com.glines.socketio.server.transport.jetty.JettyWebSocketTransport diff --git a/extension/jetty/src/main/resources/META-INF/services/com.glines.socketio.server.TransportHandler b/extension/jetty/src/main/resources/META-INF/services/com.glines.socketio.server.TransportHandler new file mode 100644 index 0000000..4b27902 --- /dev/null +++ b/extension/jetty/src/main/resources/META-INF/services/com.glines.socketio.server.TransportHandler @@ -0,0 +1,2 @@ +com.glines.socketio.server.transport.jetty.JettyWebSocketTransportHandler +com.glines.socketio.server.transport.jetty.JettyContinuationTransportHandler diff --git a/extension/jre/pom.xml b/extension/jre/pom.xml new file mode 100644 index 0000000..680eac8 --- /dev/null +++ b/extension/jre/pom.xml @@ -0,0 +1,31 @@ + + + + 4.0.0 + + + com.glines.socketio.extension + socketio-extension + 0.1-SNAPSHOT + .. + + + socketio-jre + jar + + Socket.IO Extension JRE + Socket.IO Java JRE Extension + + + + com.glines.socketio + socketio-core + + + org.eclipse.jetty + jetty-client + + + + diff --git a/core/src/main/java/com/glines/socketio/client/jre/SocketIOConnectionImpl.java b/extension/jre/src/main/java/com/glines/socketio/client/jre/SocketIOConnectionImpl.java similarity index 53% rename from core/src/main/java/com/glines/socketio/client/jre/SocketIOConnectionImpl.java rename to extension/jre/src/main/java/com/glines/socketio/client/jre/SocketIOConnectionImpl.java index 18a6a59..5a010ed 100644 --- a/core/src/main/java/com/glines/socketio/client/jre/SocketIOConnectionImpl.java +++ b/extension/jre/src/main/java/com/glines/socketio/client/jre/SocketIOConnectionImpl.java @@ -2,6 +2,8 @@ * The MIT License * Copyright (c) 2010 Tad Glines * + * Contributors: Ovea.com, Mycila.com + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights @@ -22,67 +24,61 @@ */ package com.glines.socketio.client.jre; -import org.eclipse.jetty.client.HttpClient; - import com.glines.socketio.client.common.SocketIOConnection; +import com.glines.socketio.client.common.SocketIOConnectionListener; import com.glines.socketio.common.ConnectionState; import com.glines.socketio.common.DisconnectReason; import com.glines.socketio.common.SocketIOException; +import org.eclipse.jetty.client.HttpClient; public class SocketIOConnectionImpl implements SocketIOConnection { - private final SocketIOConnection.SocketIOConnectionListener listener; - private final String host; - private final short port; - private HttpClient client; + private final SocketIOConnectionListener listener; + private final String host; + private final short port; + private HttpClient client; + + public SocketIOConnectionImpl(SocketIOConnectionListener listener, + String host, short port) { + this.listener = listener; + this.host = host; + this.port = port; + } + + @Override + public void connect() { + if (client == null) { + client = new HttpClient(); + client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL); + try { + client.start(); + } catch (Exception e) { + client = null; + listener.onDisconnect(DisconnectReason.ERROR, "Failed to initialize"); + } + } + + } - public SocketIOConnectionImpl(SocketIOConnection.SocketIOConnectionListener listener, - String host, short port) { - this.listener = listener; - this.host = host; - this.port = port; - } - - @Override - public void connect() { - if (client == null) { - client = new HttpClient(); - client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL); - try { - client.start(); - } catch (Exception e) { - client = null; - listener.onDisconnect(DisconnectReason.ERROR, "Failed to initialize"); - } - } - - } + @Override + public void close() { - @Override - public void close() { - // TODO Auto-generated method stub - - } + } - @Override - public void disconnect() { - // TODO Auto-generated method stub - - } + @Override + public void disconnect() { + } - @Override - public ConnectionState getConnectionState() { - // TODO Auto-generated method stub - return null; - } + @Override + public ConnectionState getConnectionState() { + return null; + } - @Override - public void sendMessage(String message) throws SocketIOException { - sendMessage(0, message); - } + @Override + public void sendMessage(String message) throws SocketIOException { + sendMessage(0, message); + } - @Override - public void sendMessage(int messageType, String message) throws SocketIOException { - // TODO Auto-generated method stub - - } + @Override + public void sendMessage(int messageType, String message) throws SocketIOException { + } } diff --git a/core/src/main/java/com/glines/socketio/client/jre/SocketIOConnectionXHRBase.java b/extension/jre/src/main/java/com/glines/socketio/client/jre/SocketIOConnectionXHRBase.java similarity index 97% rename from core/src/main/java/com/glines/socketio/client/jre/SocketIOConnectionXHRBase.java rename to extension/jre/src/main/java/com/glines/socketio/client/jre/SocketIOConnectionXHRBase.java index 8eb8d12..bb0b84a 100644 --- a/core/src/main/java/com/glines/socketio/client/jre/SocketIOConnectionXHRBase.java +++ b/extension/jre/src/main/java/com/glines/socketio/client/jre/SocketIOConnectionXHRBase.java @@ -2,6 +2,8 @@ * The MIT License * Copyright (c) 2010 Tad Glines * + * Contributors: Ovea.com, Mycila.com + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights @@ -34,6 +36,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; +import com.glines.socketio.client.common.SocketIOConnectionListener; import org.eclipse.jetty.client.Address; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.HttpExchange; @@ -48,7 +51,7 @@ import com.glines.socketio.server.SocketIOFrame; public class SocketIOConnectionXHRBase implements SocketIOConnection { - private final SocketIOConnection.SocketIOConnectionListener listener; + private final SocketIOConnectionListener listener; private final String host; private final short port; private final String transport; @@ -68,7 +71,7 @@ public class SocketIOConnectionXHRBase implements SocketIOConnection { protected String closeId = null; protected boolean disconnectWhenEmpty = false; - public SocketIOConnectionXHRBase(SocketIOConnection.SocketIOConnectionListener listener, + public SocketIOConnectionXHRBase(SocketIOConnectionListener listener, String host, short port, String transport, boolean isConnectionPersistent) { this.listener = listener; this.host = host; diff --git a/extension/pom.xml b/extension/pom.xml index 964d960..47f6ba8 100644 --- a/extension/pom.xml +++ b/extension/pom.xml @@ -19,6 +19,8 @@ Socket.IO Java Extensions + jetty + jre gwt diff --git a/licenses/MIT b/licenses/MIT index 11797d2..c90a954 100644 --- a/licenses/MIT +++ b/licenses/MIT @@ -1,6 +1,8 @@ The MIT License Copyright (c) ${year} ${owner} +Contributors: ${contributors} + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights diff --git a/pom.xml b/pom.xml index b33c0ce..bf2a2c3 100644 --- a/pom.xml +++ b/pom.xml @@ -62,33 +62,6 @@ - - - - mc-release - dav:https://mc-repo.googlecode.com/svn/maven2/releases - - - - mc-snapshot - dav:https://mc-repo.googlecode.com/svn/maven2/snapshots - false - - - - website - website - file://${basedir}/target/dist/site - - - github https://github.com/tadglines/Socket.IO-Java/issues @@ -111,6 +84,14 @@ samples + + + website + website + file://${basedir}/target/dist/site + + + @@ -213,11 +194,11 @@ maven-license-plugin 1.9.0 -

https://github.com/tadglines/Socket.IO-Java/raw/master/licenses/MIT
true Tad Glines ${project.inceptionYear} + Ovea.com, Mycila.com .idea/** @@ -277,7 +258,6 @@ jar - test-jar @@ -288,7 +268,6 @@ jar - test-jar @@ -304,29 +283,6 @@ - - maven-javadoc-plugin - - - - jar - - - - - - @@ -363,17 +319,59 @@ socketio-core ${project.version} + + com.glines.socketio.extension + socketio-jetty + ${project.version} + com.glines.socketio.extension socketio-gwt ${project.version} + + + org.mortbay.jetty + servlet-api + 3.0.20100224 + + + org.eclipse.jetty + jetty-client + 8.0.0.v20110901 + + + org.eclipse.jetty + jetty-continuation + 8.0.0.v20110901 + + + org.eclipse.jetty + jetty-websocket + 8.0.0.v20110901 + + + org.eclipse.jetty + jetty-server + 8.0.0.v20110901 + + + + org.eclipse.jetty.aggregate + jetty-all + 8.0.0.v20110901 + org.testatoo.container testatoo-container-jetty 1.0-rc1 + + org.testatoo.container + testatoo-container-tomcat + 1.0-rc1 + com.google.gwt @@ -385,7 +383,28 @@ gwt-user 2.1.1 + + + log4j + log4j + 1.2.16 + + + org.slf4j + slf4j-log4j12 + 1.6.1 + + + org.slf4j + slf4j-api + 1.6.1 + + + com.google.code.gson + gson + 1.4 + org.codehaus.jettison jettison @@ -400,4 +419,99 @@ + + + + release + + + performRelease + true + + + + + socketio-staging + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + socketio-snapshots + https://oss.sonatype.org/content/repositories/snapshots/ + false + + + + + + maven-javadoc-plugin + + + + jar + + + + + + maven-gpg-plugin + + + sign-artifacts + verify + + sign + + + + + + + + + + default + + true + + + + + com.mycila.maven-license-plugin + maven-license-plugin + +
https://github.com/tadglines/Socket.IO-Java/raw/master/licenses/MIT
+
+
+
+
+
+ + + ovea + + + mc-release + dav:https://mc-repo.googlecode.com/svn/maven2/releases + + + mc-snapshot + dav:https://mc-repo.googlecode.com/svn/maven2/snapshots + false + + + + + + com.mycila.maven-license-plugin + maven-license-plugin + +
https://github.com/Ovea/Socket.IO-Java/raw/master/licenses/MIT
+
+
+
+
+
+ +
+ diff --git a/samples/broadcast/pom.xml b/samples/broadcast/pom.xml index 76a3396..6b6dff1 100644 --- a/samples/broadcast/pom.xml +++ b/samples/broadcast/pom.xml @@ -23,8 +23,13 @@ socketio-core
- org.testatoo.container - testatoo-container-jetty + com.glines.socketio.extension + socketio-jetty + + + + org.mortbay.jetty + servlet-api provided diff --git a/samples/broadcast/src/main/java/com/glines/socketio/sample/broadcast/BroadcastSocketServlet.java b/samples/broadcast/src/main/java/com/glines/socketio/sample/broadcast/BroadcastSocketServlet.java index 63bc30d..c14aab6 100644 --- a/samples/broadcast/src/main/java/com/glines/socketio/sample/broadcast/BroadcastSocketServlet.java +++ b/samples/broadcast/src/main/java/com/glines/socketio/sample/broadcast/BroadcastSocketServlet.java @@ -2,6 +2,8 @@ * The MIT License * Copyright (c) 2010 Tad Glines * + * Contributors: Ovea.com, Mycila.com + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights @@ -22,60 +24,56 @@ */ package com.glines.socketio.sample.broadcast; -import java.io.IOException; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; - -import javax.servlet.http.HttpServletRequest; - -import com.glines.socketio.server.SocketIOOutbound; -import org.eclipse.jetty.util.log.Log; - import com.glines.socketio.common.DisconnectReason; import com.glines.socketio.server.SocketIOInbound; +import com.glines.socketio.server.SocketIOOutbound; import com.glines.socketio.server.SocketIOServlet; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; + public class BroadcastSocketServlet extends SocketIOServlet { - private static final long serialVersionUID = 1L; - private Queue connections = new ConcurrentLinkedQueue(); + private static final long serialVersionUID = 1L; + private Queue connections = new ConcurrentLinkedQueue(); - private class BroadcastConnection implements SocketIOInbound { - private volatile SocketIOOutbound outbound = null; + private class BroadcastConnection implements SocketIOInbound { + private volatile SocketIOOutbound outbound = null; - @Override - public void onConnect(SocketIOOutbound outbound) { - this.outbound = outbound; + @Override + public void onConnect(SocketIOOutbound outbound) { + this.outbound = outbound; connections.offer(this); - } + } - @Override - public void onDisconnect(DisconnectReason reason, String errorMessage) { - this.outbound = null; - connections.remove(this); - } + @Override + public void onDisconnect(DisconnectReason reason, String errorMessage) { + this.outbound = null; + connections.remove(this); + } - @Override - public void onMessage(int messageType, String message) { - broadcast(messageType, message); - } + @Override + public void onMessage(int messageType, String message) { + broadcast(messageType, message); + } - private void broadcast(int messageType, String message) { - Log.debug("Broadcasting: " + message); - for(BroadcastConnection c: connections) { - if (c != this) { - try { - c.outbound.sendMessage(messageType, message); - } catch (IOException e) { - c.outbound.disconnect(); - } - } - } - } - } + private void broadcast(int messageType, String message) { + for (BroadcastConnection c : connections) { + if (c != this) { + try { + c.outbound.sendMessage(messageType, message); + } catch (IOException e) { + c.outbound.disconnect(); + } + } + } + } + } - @Override - protected SocketIOInbound doSocketIOConnect(HttpServletRequest request) { - return new BroadcastConnection(); - } + @Override + protected SocketIOInbound doSocketIOConnect(HttpServletRequest request) { + return new BroadcastConnection(); + } } diff --git a/samples/eventbus/src/main/resources/logging.properties b/samples/broadcast/src/main/resources/logging.properties similarity index 61% rename from samples/eventbus/src/main/resources/logging.properties rename to samples/broadcast/src/main/resources/logging.properties index 72acbe3..308fdd7 100644 --- a/samples/eventbus/src/main/resources/logging.properties +++ b/samples/broadcast/src/main/resources/logging.properties @@ -1,4 +1,4 @@ handlers=java.util.logging.ConsoleHandler java.util.logging.ConsoleHandler.level=ALL -com.glines.socketio.sample.eventbus.EventBusServlet.level=ALL +com.glines.level=ALL .level=INFO diff --git a/samples/broadcast/src/main/webapp/WEB-INF/web.xml b/samples/broadcast/src/main/webapp/WEB-INF/web.xml index c9d3c0b..3ab942e 100644 --- a/samples/broadcast/src/main/webapp/WEB-INF/web.xml +++ b/samples/broadcast/src/main/webapp/WEB-INF/web.xml @@ -4,6 +4,8 @@ The MIT License Copyright (c) 2010 Tad Glines + Contributors: Ovea.com, Mycila.com + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights diff --git a/samples/broadcast/src/main/java/com/glines/socketio/sample/broadcast/BroadcastServer.java b/samples/broadcast/src/test/java/BroadcastServer.java similarity index 84% rename from samples/broadcast/src/main/java/com/glines/socketio/sample/broadcast/BroadcastServer.java rename to samples/broadcast/src/test/java/BroadcastServer.java index f27ff86..5c7ffcf 100644 --- a/samples/broadcast/src/main/java/com/glines/socketio/sample/broadcast/BroadcastServer.java +++ b/samples/broadcast/src/test/java/BroadcastServer.java @@ -2,6 +2,8 @@ * The MIT License * Copyright (c) 2010 Tad Glines * + * Contributors: Ovea.com, Mycila.com + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights @@ -20,8 +22,8 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.glines.socketio.sample.broadcast; - +import com.glines.socketio.sample.broadcast.BroadcastSocketServlet; +import com.glines.socketio.server.SocketIOConfig; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.nio.SelectChannelConnector; import org.eclipse.jetty.servlet.ServletContextHandler; @@ -55,9 +57,9 @@ public static void main(String[] args) throws Exception { ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); ServletHolder holder = new ServletHolder(new BroadcastSocketServlet()); - holder.setInitParameter(FlashSocketTransport.FLASHPOLICY_SERVER_HOST_KEY, host); - holder.setInitParameter(FlashSocketTransport.FLASHPOLICY_DOMAIN_KEY, host); - holder.setInitParameter(FlashSocketTransport.FLASHPOLICY_PORTS_KEY, ""+ port); + holder.setInitParameter(FlashSocketTransport.PARAM_FLASHPOLICY_SERVER_HOST, host); + holder.setInitParameter(FlashSocketTransport.PARAM_FLASHPOLICY_DOMAIN, host); + holder.setInitParameter(FlashSocketTransport.PARAM_FLASHPOLICY_PORTS, ""+ port); context.addServlet(holder, "/socket.io/*"); server.setHandler(context); diff --git a/samples/broadcast/src/test/java/StartSampleBroadcast.java b/samples/broadcast/src/test/java/StartSampleBroadcast.java index 810eb3e..d70b1e8 100644 --- a/samples/broadcast/src/test/java/StartSampleBroadcast.java +++ b/samples/broadcast/src/test/java/StartSampleBroadcast.java @@ -2,6 +2,8 @@ * The MIT License * Copyright (c) 2010 Tad Glines * + * Contributors: Ovea.com, Mycila.com + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights diff --git a/samples/chat-gwt/pom.xml b/samples/chat-gwt/pom.xml index 9f3ff3d..920174b 100644 --- a/samples/chat-gwt/pom.xml +++ b/samples/chat-gwt/pom.xml @@ -52,20 +52,29 @@ com.glines.socketio socketio-core + + com.glines.socketio.extension + socketio-jetty + com.glines.socketio.extension socketio-gwt - org.testatoo.container - testatoo-container-jetty - provided + com.google.code.gson + gson + com.google.gwt gwt-user provided + + org.mortbay.jetty + servlet-api + provided + diff --git a/samples/chat-gwt/src/main/java/com/glines/socketio/sample/gwtchat/GWTChatSocketServlet.java b/samples/chat-gwt/src/main/java/com/glines/socketio/sample/gwtchat/GWTChatSocketServlet.java index 9ed183b..74d1fd5 100644 --- a/samples/chat-gwt/src/main/java/com/glines/socketio/sample/gwtchat/GWTChatSocketServlet.java +++ b/samples/chat-gwt/src/main/java/com/glines/socketio/sample/gwtchat/GWTChatSocketServlet.java @@ -2,6 +2,8 @@ * The MIT License * Copyright (c) 2010 Tad Glines * + * Contributors: Ovea.com, Mycila.com + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights @@ -22,103 +24,98 @@ */ package com.glines.socketio.sample.gwtchat; -import java.io.IOException; -import java.util.Collections; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.atomic.AtomicInteger; - -import javax.servlet.http.HttpServletRequest; - -import com.glines.socketio.server.SocketIOOutbound; -import org.eclipse.jetty.util.ajax.JSON; -import org.eclipse.jetty.util.log.Log; - import com.glines.socketio.common.DisconnectReason; import com.glines.socketio.common.SocketIOException; import com.glines.socketio.server.SocketIOFrame; import com.glines.socketio.server.SocketIOInbound; +import com.glines.socketio.server.SocketIOOutbound; import com.glines.socketio.server.SocketIOServlet; +import com.google.gson.Gson; + +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.util.Collections; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicInteger; public class GWTChatSocketServlet extends SocketIOServlet { - private static final long serialVersionUID = 1L; - private AtomicInteger ids = new AtomicInteger(1); - private Queue connections = new ConcurrentLinkedQueue(); + private static final long serialVersionUID = 1L; + private AtomicInteger ids = new AtomicInteger(1); + private Queue connections = new ConcurrentLinkedQueue(); - private class GWTChatConnection implements SocketIOInbound { - private volatile SocketIOOutbound outbound = null; - private Integer sessionId = ids.getAndIncrement(); + private class GWTChatConnection implements SocketIOInbound { + private volatile SocketIOOutbound outbound = null; + private Integer sessionId = ids.getAndIncrement(); - @Override - public void onConnect(SocketIOOutbound outbound) { - this.outbound = outbound; + @Override + public void onConnect(SocketIOOutbound outbound) { + this.outbound = outbound; connections.offer(this); - try { - outbound.sendMessage(SocketIOFrame.JSON_MESSAGE_TYPE, JSON.toString( - Collections.singletonMap("welcome", "Welcome to GWT Chat!"))); - } catch (SocketIOException e) { - outbound.disconnect(); - } - broadcast(SocketIOFrame.JSON_MESSAGE_TYPE, JSON.toString( - Collections.singletonMap("announcement", sessionId + " connected"))); - } + try { + outbound.sendMessage(SocketIOFrame.JSON_MESSAGE_TYPE, new Gson().toJson( + Collections.singletonMap("welcome", "Welcome to GWT Chat!"))); + } catch (SocketIOException e) { + outbound.disconnect(); + } + broadcast(SocketIOFrame.JSON_MESSAGE_TYPE, new Gson().toJson( + Collections.singletonMap("announcement", sessionId + " connected"))); + } - @Override - public void onDisconnect(DisconnectReason reason, String errorMessage) { - this.outbound = null; - connections.remove(this); - broadcast(SocketIOFrame.JSON_MESSAGE_TYPE, JSON.toString( - Collections.singletonMap("announcement", sessionId + " disconnected"))); - } + @Override + public void onDisconnect(DisconnectReason reason, String errorMessage) { + this.outbound = null; + connections.remove(this); + broadcast(SocketIOFrame.JSON_MESSAGE_TYPE, new Gson().toJson( + Collections.singletonMap("announcement", sessionId + " disconnected"))); + } - @Override - public void onMessage(int messageType, String message) { - Log.debug("Recieved: " + message); - if (message.equals("/rclose")) { - outbound.close(); - } else if (message.equals("/rdisconnect")) { - outbound.disconnect(); - } else if (message.startsWith("/sleep")) { - int sleepTime = 1; - String parts[] = message.split("\\s+"); - if (parts.length == 2) { - sleepTime = Integer.parseInt(parts[1]); - } - try { - Thread.sleep(sleepTime * 1000); - } catch (InterruptedException e) { - // Ignore - } - try { - outbound.sendMessage(SocketIOFrame.JSON_MESSAGE_TYPE, JSON.toString( - Collections.singletonMap("message","Slept for " + sleepTime + " seconds."))); - } catch (SocketIOException e) { - outbound.disconnect(); - } - } else { - broadcast(SocketIOFrame.JSON_MESSAGE_TYPE, JSON.toString( - Collections.singletonMap("message", - new String[]{sessionId.toString(), (String)message}))); - } - } + @Override + public void onMessage(int messageType, String message) { + if (message.equals("/rclose")) { + outbound.close(); + } else if (message.equals("/rdisconnect")) { + outbound.disconnect(); + } else if (message.startsWith("/sleep")) { + int sleepTime = 1; + String parts[] = message.split("\\s+"); + if (parts.length == 2) { + sleepTime = Integer.parseInt(parts[1]); + } + try { + Thread.sleep(sleepTime * 1000); + } catch (InterruptedException e) { + // Ignore + } + try { + outbound.sendMessage(SocketIOFrame.JSON_MESSAGE_TYPE, new Gson().toJson( + Collections.singletonMap("message", "Slept for " + sleepTime + " seconds."))); + } catch (SocketIOException e) { + outbound.disconnect(); + } + } else { + broadcast(SocketIOFrame.JSON_MESSAGE_TYPE, new Gson().toJson( + Collections.singletonMap("message", + new String[]{sessionId.toString(), (String) message}))); + } + } - private void broadcast(int messageType, String message) { - Log.debug("Broadcasting: " + message); - for(GWTChatConnection c: connections) { - if (c != this) { - try { - c.outbound.sendMessage(messageType, message); - } catch (IOException e) { - c.outbound.disconnect(); - } - } - } - } - } + private void broadcast(int messageType, String message) { + for (GWTChatConnection c : connections) { + if (c != this) { + try { + c.outbound.sendMessage(messageType, message); + } catch (IOException e) { + c.outbound.disconnect(); + } + } + } + } + } - @Override - protected SocketIOInbound doSocketIOConnect(HttpServletRequest request) { - return new GWTChatConnection(); - } + @Override + protected SocketIOInbound doSocketIOConnect(HttpServletRequest request) { + return new GWTChatConnection(); + } } diff --git a/samples/chat-gwt/src/main/java/com/glines/socketio/sample/gwtchat/client/GWTChatClient.java b/samples/chat-gwt/src/main/java/com/glines/socketio/sample/gwtchat/client/GWTChatClient.java index 5f6a6fe..9da9157 100644 --- a/samples/chat-gwt/src/main/java/com/glines/socketio/sample/gwtchat/client/GWTChatClient.java +++ b/samples/chat-gwt/src/main/java/com/glines/socketio/sample/gwtchat/client/GWTChatClient.java @@ -2,6 +2,8 @@ * The MIT License * Copyright (c) 2010 Tad Glines * + * Contributors: Ovea.com, Mycila.com + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights @@ -23,6 +25,7 @@ package com.glines.socketio.sample.gwtchat.client; import com.glines.socketio.client.common.SocketIOConnection; +import com.glines.socketio.client.common.SocketIOConnectionListener; import com.glines.socketio.client.gwt.GWTSocketIOConnectionFactory; import com.glines.socketio.common.DisconnectReason; import com.glines.socketio.common.SocketIOException; @@ -44,7 +47,7 @@ import com.google.gwt.json.client.JSONParser; import com.google.gwt.json.client.JSONString; -public class GWTChatClient implements EntryPoint, SocketIOConnection.SocketIOConnectionListener { +public class GWTChatClient implements EntryPoint, SocketIOConnectionListener { SocketIOConnection socket; HTML htmlPanel; @@ -159,7 +162,7 @@ private void onMessage(JSONObject obj) { @Override public void onMessage(int messageType, String message) { if (messageType == 1) { - JSONObject obj = JSONParser.parse(message).isObject(); + JSONObject obj = JSONParser.parseStrict(message).isObject(); if (obj != null) { onMessage(obj); } diff --git a/samples/chat-gwt/src/main/resources/com/glines/socketio/sample/gwtchat/GWTChat.gwt.xml b/samples/chat-gwt/src/main/resources/com/glines/socketio/sample/gwtchat/GWTChat.gwt.xml index f3cf832..4c394d3 100644 --- a/samples/chat-gwt/src/main/resources/com/glines/socketio/sample/gwtchat/GWTChat.gwt.xml +++ b/samples/chat-gwt/src/main/resources/com/glines/socketio/sample/gwtchat/GWTChat.gwt.xml @@ -3,6 +3,8 @@ The MIT License Copyright (c) 2010 Tad Glines + Contributors: Ovea.com, Mycila.com + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights diff --git a/samples/chat-gwt/src/main/webapp/WEB-INF/web.xml b/samples/chat-gwt/src/main/webapp/WEB-INF/web.xml index bba59d7..e647af4 100644 --- a/samples/chat-gwt/src/main/webapp/WEB-INF/web.xml +++ b/samples/chat-gwt/src/main/webapp/WEB-INF/web.xml @@ -4,6 +4,8 @@ The MIT License Copyright (c) 2010 Tad Glines + Contributors: Ovea.com, Mycila.com + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights diff --git a/samples/chat-gwt/src/main/webapp/gwtchat.html b/samples/chat-gwt/src/main/webapp/gwtchat.html index 1f1f4e8..13fd188 100644 --- a/samples/chat-gwt/src/main/webapp/gwtchat.html +++ b/samples/chat-gwt/src/main/webapp/gwtchat.html @@ -3,6 +3,8 @@ The MIT License Copyright (c) 2010 Tad Glines + Contributors: Ovea.com, Mycila.com + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights diff --git a/samples/chat-gwt/src/main/java/com/glines/socketio/sample/gwtchat/GWTChatServer.java b/samples/chat-gwt/src/test/java/GWTChatServer.java similarity index 90% rename from samples/chat-gwt/src/main/java/com/glines/socketio/sample/gwtchat/GWTChatServer.java rename to samples/chat-gwt/src/test/java/GWTChatServer.java index 9d6d24a..44006d2 100644 --- a/samples/chat-gwt/src/main/java/com/glines/socketio/sample/gwtchat/GWTChatServer.java +++ b/samples/chat-gwt/src/test/java/GWTChatServer.java @@ -2,6 +2,8 @@ * The MIT License * Copyright (c) 2010 Tad Glines * + * Contributors: Ovea.com, Mycila.com + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights @@ -20,24 +22,22 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.glines.socketio.sample.gwtchat; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import com.glines.socketio.sample.gwtchat.GWTChatSocketServlet; +import com.glines.socketio.server.transport.FlashSocketTransport; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.nio.SelectChannelConnector; import org.eclipse.jetty.servlet.DefaultServlet; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; -import com.glines.socketio.server.transport.FlashSocketTransport; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; public class GWTChatServer { private static class StaticServlet extends HttpServlet { @@ -93,9 +93,9 @@ public static void main(String[] args) throws Exception { ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); context.setResourceBase(war_dir); ServletHolder holder = new ServletHolder(new GWTChatSocketServlet()); - holder.setInitParameter(FlashSocketTransport.FLASHPOLICY_SERVER_HOST_KEY, host); - holder.setInitParameter(FlashSocketTransport.FLASHPOLICY_DOMAIN_KEY, host); - holder.setInitParameter(FlashSocketTransport.FLASHPOLICY_PORTS_KEY, ""+ port); + holder.setInitParameter(FlashSocketTransport.PARAM_FLASHPOLICY_SERVER_HOST, host); + holder.setInitParameter(FlashSocketTransport.PARAM_FLASHPOLICY_DOMAIN, host); + holder.setInitParameter(FlashSocketTransport.PARAM_FLASHPOLICY_PORTS, ""+ port); context.addServlet(holder, "/socket.io/*"); context.addServlet(new ServletHolder(new StaticServlet()), "/gwtchat.html"); ServletHolder defaultServlet = new ServletHolder(new DefaultServlet()); diff --git a/samples/chat-gwt/src/test/java/StartSampleChatGWT.java b/samples/chat-gwt/src/test/java/StartSampleChatGWT.java index 2e22fa2..2a1db67 100644 --- a/samples/chat-gwt/src/test/java/StartSampleChatGWT.java +++ b/samples/chat-gwt/src/test/java/StartSampleChatGWT.java @@ -2,6 +2,8 @@ * The MIT License * Copyright (c) 2010 Tad Glines * + * Contributors: Ovea.com, Mycila.com + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights diff --git a/samples/chat/pom.xml b/samples/chat/pom.xml index 725ebdc..addbdab 100644 --- a/samples/chat/pom.xml +++ b/samples/chat/pom.xml @@ -23,8 +23,31 @@ socketio-core - org.testatoo.container - testatoo-container-jetty + com.glines.socketio.extension + socketio-jetty + + + com.google.code.gson + gson + + + + + log4j + log4j + + + org.slf4j + slf4j-log4j12 + + + org.slf4j + slf4j-api + + + + org.mortbay.jetty + servlet-api provided diff --git a/samples/chat/src/main/java/com/glines/socketio/sample/chat/ChatSocketServlet.java b/samples/chat/src/main/java/com/glines/socketio/sample/chat/ChatSocketServlet.java index 8fc5887..b289f1c 100644 --- a/samples/chat/src/main/java/com/glines/socketio/sample/chat/ChatSocketServlet.java +++ b/samples/chat/src/main/java/com/glines/socketio/sample/chat/ChatSocketServlet.java @@ -2,6 +2,8 @@ * The MIT License * Copyright (c) 2010 Tad Glines * + * Contributors: Ovea.com, Mycila.com + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights @@ -22,137 +24,138 @@ */ package com.glines.socketio.sample.chat; -import java.io.IOException; -import java.util.Collections; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.atomic.AtomicInteger; - -import javax.servlet.http.HttpServletRequest; - -import com.glines.socketio.server.SocketIOOutbound; -import org.eclipse.jetty.util.ajax.JSON; -import org.eclipse.jetty.util.log.Log; - import com.glines.socketio.common.DisconnectReason; import com.glines.socketio.common.SocketIOException; import com.glines.socketio.server.SocketIOFrame; import com.glines.socketio.server.SocketIOInbound; +import com.glines.socketio.server.SocketIOOutbound; import com.glines.socketio.server.SocketIOServlet; +import com.glines.socketio.util.JdkOverLog4j; +import com.google.gson.Gson; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.util.Collections; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicInteger; public class ChatSocketServlet extends SocketIOServlet { - private static final long serialVersionUID = 1L; - private AtomicInteger ids = new AtomicInteger(1); - private Queue connections = new ConcurrentLinkedQueue(); + private static final long serialVersionUID = 1L; + private AtomicInteger ids = new AtomicInteger(1); + private Queue connections = new ConcurrentLinkedQueue(); + + @Override + public void init(ServletConfig config) throws ServletException { + JdkOverLog4j.install(); + super.init(config); + } - private class ChatConnection implements SocketIOInbound { - private volatile SocketIOOutbound outbound = null; - private Integer sessionId = ids.getAndIncrement(); + private class ChatConnection implements SocketIOInbound { + private volatile SocketIOOutbound outbound = null; + private Integer sessionId = ids.getAndIncrement(); - @Override - public void onConnect(SocketIOOutbound outbound) { - this.outbound = outbound; + @Override + public void onConnect(SocketIOOutbound outbound) { + this.outbound = outbound; connections.offer(this); - try { - outbound.sendMessage(SocketIOFrame.JSON_MESSAGE_TYPE, JSON.toString( - Collections.singletonMap("welcome", "Welcome to Socket.IO Chat!"))); - } catch (SocketIOException e) { - outbound.disconnect(); - } - broadcast(SocketIOFrame.JSON_MESSAGE_TYPE, JSON.toString( - Collections.singletonMap("announcement", sessionId + " connected"))); - } + try { + outbound.sendMessage(SocketIOFrame.JSON_MESSAGE_TYPE, new Gson().toJson( + Collections.singletonMap("welcome", "Welcome to Socket.IO Chat!"))); + } catch (SocketIOException e) { + outbound.disconnect(); + } + broadcast(SocketIOFrame.JSON_MESSAGE_TYPE, new Gson().toJson( + Collections.singletonMap("announcement", sessionId + " connected"))); + } - @Override - public void onDisconnect(DisconnectReason reason, String errorMessage) { - this.outbound = null; - connections.remove(this); - broadcast(SocketIOFrame.JSON_MESSAGE_TYPE, JSON.toString( - Collections.singletonMap("announcement", sessionId + " disconnected"))); - } + @Override + public void onDisconnect(DisconnectReason reason, String errorMessage) { + this.outbound = null; + connections.remove(this); + broadcast(SocketIOFrame.JSON_MESSAGE_TYPE, new Gson().toJson( + Collections.singletonMap("announcement", sessionId + " disconnected"))); + } - @Override - public void onMessage(int messageType, String message) { - Log.debug("Recieved: " + message); - if (message.equals("/rclose")) { - outbound.close(); - } else if (message.equals("/rdisconnect")) { - outbound.disconnect(); - } else if (message.startsWith("/sleep")) { - int sleepTime = 1; - String parts[] = message.split("\\s+"); - if (parts.length == 2) { - sleepTime = Integer.parseInt(parts[1]); - } - try { - Thread.sleep(sleepTime * 1000); - } catch (InterruptedException e) { - // Ignore - } - try { - outbound.sendMessage(SocketIOFrame.JSON_MESSAGE_TYPE, JSON.toString( - Collections.singletonMap("message","Slept for " + sleepTime + " seconds."))); - } catch (SocketIOException e) { - outbound.disconnect(); - } - } else if (message.startsWith("/burst")) { - int burstNum = 10; - String parts[] = message.split("\\s+"); - if (parts.length == 2) { - burstNum = Integer.parseInt(parts[1]); - } - try { - for (int i = 0; i < burstNum; i++) { - Log.debug("**************************************** Sending burst message: " + i); - outbound.sendMessage(SocketIOFrame.JSON_MESSAGE_TYPE, JSON.toString( - Collections.singletonMap("message",new String[]{"Server", "Hi " + i + - "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF" + - "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF" + - "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF" + - "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF" + - "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF" + - "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF" + - "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF" + - "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF" - }))); + @Override + public void onMessage(int messageType, String message) { + if (message.equals("/rclose")) { + outbound.close(); + } else if (message.equals("/rdisconnect")) { + outbound.disconnect(); + } else if (message.startsWith("/sleep")) { + int sleepTime = 1; + String parts[] = message.split("\\s+"); + if (parts.length == 2) { + sleepTime = Integer.parseInt(parts[1]); + } + try { + Thread.sleep(sleepTime * 1000); + } catch (InterruptedException e) { + // Ignore + } + try { + outbound.sendMessage(SocketIOFrame.JSON_MESSAGE_TYPE, new Gson().toJson( + Collections.singletonMap("message", "Slept for " + sleepTime + " seconds."))); + } catch (SocketIOException e) { + outbound.disconnect(); + } + } else if (message.startsWith("/burst")) { + int burstNum = 10; + String parts[] = message.split("\\s+"); + if (parts.length == 2) { + burstNum = Integer.parseInt(parts[1]); + } + try { + for (int i = 0; i < burstNum; i++) { + outbound.sendMessage(SocketIOFrame.JSON_MESSAGE_TYPE, new Gson().toJson( + Collections.singletonMap("message", new String[]{"Server", "Hi " + i + + "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF" + + "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF" + + "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF" + + "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF" + + "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF" + + "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF" + + "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF" + + "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF" + }))); // outbound.sendMessage(SocketIOFrame.JSON_MESSAGE_TYPE, JSON.toString( // Collections.singletonMap("say","Hi " + i))); - try { - Thread.sleep(250); - } catch (InterruptedException e) { - // Do nothing - } - } - } catch (Exception e) { - Log.debug(e); + try { + Thread.sleep(250); + } catch (InterruptedException e) { + // Do nothing + } + } + } catch (Exception e) { // } catch (SocketIOException e) { -// Log.debug(e); // outbound.disconnect(); - } - } else { - broadcast(SocketIOFrame.JSON_MESSAGE_TYPE, JSON.toString( - Collections.singletonMap("message", - new String[]{sessionId.toString(), (String)message}))); - } - } + } + } else { + broadcast(SocketIOFrame.JSON_MESSAGE_TYPE, new Gson().toJson( + Collections.singletonMap("message", + new String[]{sessionId.toString(), (String) message}))); + } + } - private void broadcast(int messageType, String message) { - Log.debug("Broadcasting: " + message); - for(ChatConnection c: connections) { - if (c != this) { - try { - c.outbound.sendMessage(messageType, message); - } catch (IOException e) { - c.outbound.disconnect(); - } - } - } - } - } + private void broadcast(int messageType, String message) { + for (ChatConnection c : connections) { + if (c != this) { + try { + c.outbound.sendMessage(messageType, message); + } catch (IOException e) { + c.outbound.disconnect(); + } + } + } + } + } - @Override - protected SocketIOInbound doSocketIOConnect(HttpServletRequest request) { - return new ChatConnection(); - } + @Override + protected SocketIOInbound doSocketIOConnect(HttpServletRequest request) { + return new ChatConnection(); + } } diff --git a/samples/chat/src/main/resources/log4j.xml b/samples/chat/src/main/resources/log4j.xml new file mode 100644 index 0000000..d33d3d6 --- /dev/null +++ b/samples/chat/src/main/resources/log4j.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/chat/src/main/webapp/META-INF/context.xml b/samples/chat/src/main/webapp/META-INF/context.xml new file mode 100644 index 0000000..c3c5b81 --- /dev/null +++ b/samples/chat/src/main/webapp/META-INF/context.xml @@ -0,0 +1,29 @@ + + + + diff --git a/samples/chat/src/main/webapp/WEB-INF/web.xml b/samples/chat/src/main/webapp/WEB-INF/web.xml index ef5d83d..ff139b8 100644 --- a/samples/chat/src/main/webapp/WEB-INF/web.xml +++ b/samples/chat/src/main/webapp/WEB-INF/web.xml @@ -4,6 +4,8 @@ The MIT License Copyright (c) 2010 Tad Glines + Contributors: Ovea.com, Mycila.com + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights diff --git a/samples/chat/src/main/webapp/chat.html b/samples/chat/src/main/webapp/chat.html index e8b29d2..e6dc417 100644 --- a/samples/chat/src/main/webapp/chat.html +++ b/samples/chat/src/main/webapp/chat.html @@ -3,6 +3,8 @@ The MIT License Copyright (c) 2010 Tad Glines + Contributors: Ovea.com, Mycila.com + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights diff --git a/samples/chat/src/main/java/com/glines/socketio/sample/chat/ChatServer.java b/samples/chat/src/test/java/ChatServer.java similarity index 90% rename from samples/chat/src/main/java/com/glines/socketio/sample/chat/ChatServer.java rename to samples/chat/src/test/java/ChatServer.java index 6d27c17..94ea1e2 100644 --- a/samples/chat/src/main/java/com/glines/socketio/sample/chat/ChatServer.java +++ b/samples/chat/src/test/java/ChatServer.java @@ -2,6 +2,8 @@ * The MIT License * Copyright (c) 2010 Tad Glines * + * Contributors: Ovea.com, Mycila.com + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights @@ -20,8 +22,6 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.glines.socketio.sample.chat; - import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -31,6 +31,8 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import com.glines.socketio.sample.chat.ChatSocketServlet; +import com.glines.socketio.server.SocketIOConfig; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.nio.SelectChannelConnector; import org.eclipse.jetty.servlet.ServletContextHandler; @@ -100,9 +102,9 @@ public static void main(String[] args) throws Exception { ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); ServletHolder holder = new ServletHolder(new ChatSocketServlet()); - holder.setInitParameter(FlashSocketTransport.FLASHPOLICY_SERVER_HOST_KEY, host); - holder.setInitParameter(FlashSocketTransport.FLASHPOLICY_DOMAIN_KEY, host); - holder.setInitParameter(FlashSocketTransport.FLASHPOLICY_PORTS_KEY, ""+ port); + holder.setInitParameter(FlashSocketTransport.PARAM_FLASHPOLICY_SERVER_HOST, host); + holder.setInitParameter(FlashSocketTransport.PARAM_FLASHPOLICY_DOMAIN, host); + holder.setInitParameter(FlashSocketTransport.PARAM_FLASHPOLICY_PORTS, ""+ port); context.addServlet(holder, "/socket.io/*"); context.addServlet(new ServletHolder(new StaticServlet()), "/*"); diff --git a/samples/chat/src/test/java/StartSampleChat.java b/samples/chat/src/test/java/StartSampleChat.java index 1480d4d..15f4dfb 100644 --- a/samples/chat/src/test/java/StartSampleChat.java +++ b/samples/chat/src/test/java/StartSampleChat.java @@ -2,6 +2,8 @@ * The MIT License * Copyright (c) 2010 Tad Glines * + * Contributors: Ovea.com, Mycila.com + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights @@ -32,5 +34,9 @@ public static void main(String... args) throws Exception { // ContainerRunner could be directly run from a launcher with argument. // See ContainerRunner --help foe help ContainerRunner.main("-container", "jetty"); + + // comment the jetty extension in pom.xml to switch to tomcat + // and activate tomcat profile in your ide + //ContainerRunner.main("-container", "tomcat"); } } diff --git a/samples/echo/pom.xml b/samples/echo/pom.xml index 4fc8e67..6e51f04 100644 --- a/samples/echo/pom.xml +++ b/samples/echo/pom.xml @@ -23,8 +23,13 @@ socketio-core - org.testatoo.container - testatoo-container-jetty + com.glines.socketio.extension + socketio-jetty + + + + org.mortbay.jetty + servlet-api provided diff --git a/samples/echo/src/main/java/com/glines/socketio/sample/echo/EchoSocketServlet.java b/samples/echo/src/main/java/com/glines/socketio/sample/echo/EchoSocketServlet.java index 802701d..54c2dea 100644 --- a/samples/echo/src/main/java/com/glines/socketio/sample/echo/EchoSocketServlet.java +++ b/samples/echo/src/main/java/com/glines/socketio/sample/echo/EchoSocketServlet.java @@ -2,6 +2,8 @@ * The MIT License * Copyright (c) 2010 Tad Glines * + * Contributors: Ovea.com, Mycila.com + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights diff --git a/samples/echo/src/main/webapp/WEB-INF/web.xml b/samples/echo/src/main/webapp/WEB-INF/web.xml index 4860c0d..915d762 100644 --- a/samples/echo/src/main/webapp/WEB-INF/web.xml +++ b/samples/echo/src/main/webapp/WEB-INF/web.xml @@ -4,6 +4,8 @@ The MIT License Copyright (c) 2010 Tad Glines + Contributors: Ovea.com, Mycila.com + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights diff --git a/samples/echo/src/main/java/com/glines/socketio/sample/echo/EchoServer.java b/samples/echo/src/test/java/EchoServer.java similarity index 84% rename from samples/echo/src/main/java/com/glines/socketio/sample/echo/EchoServer.java rename to samples/echo/src/test/java/EchoServer.java index d3b04da..126849c 100644 --- a/samples/echo/src/main/java/com/glines/socketio/sample/echo/EchoServer.java +++ b/samples/echo/src/test/java/EchoServer.java @@ -2,6 +2,8 @@ * The MIT License * Copyright (c) 2010 Tad Glines * + * Contributors: Ovea.com, Mycila.com + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights @@ -20,8 +22,8 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.glines.socketio.sample.echo; - +import com.glines.socketio.sample.echo.EchoSocketServlet; +import com.glines.socketio.server.SocketIOConfig; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.nio.SelectChannelConnector; import org.eclipse.jetty.servlet.ServletContextHandler; @@ -55,9 +57,9 @@ public static void main(String[] args) throws Exception { ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); ServletHolder holder = new ServletHolder(new EchoSocketServlet()); - holder.setInitParameter(FlashSocketTransport.FLASHPOLICY_SERVER_HOST_KEY, host); - holder.setInitParameter(FlashSocketTransport.FLASHPOLICY_DOMAIN_KEY, host); - holder.setInitParameter(FlashSocketTransport.FLASHPOLICY_PORTS_KEY, ""+ port); + holder.setInitParameter(FlashSocketTransport.PARAM_FLASHPOLICY_SERVER_HOST, host); + holder.setInitParameter(FlashSocketTransport.PARAM_FLASHPOLICY_DOMAIN, host); + holder.setInitParameter(FlashSocketTransport.PARAM_FLASHPOLICY_PORTS, ""+ port); context.addServlet(holder, "/socket.io/*"); server.setHandler(context); diff --git a/samples/echo/src/test/java/StartSampleEcho.java b/samples/echo/src/test/java/StartSampleEcho.java index 54ecbef..8011ae7 100644 --- a/samples/echo/src/test/java/StartSampleEcho.java +++ b/samples/echo/src/test/java/StartSampleEcho.java @@ -2,6 +2,8 @@ * The MIT License * Copyright (c) 2010 Tad Glines * + * Contributors: Ovea.com, Mycila.com + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights diff --git a/samples/eventbus/pom.xml b/samples/eventbus/pom.xml index 4bea2b9..c616aa4 100644 --- a/samples/eventbus/pom.xml +++ b/samples/eventbus/pom.xml @@ -22,14 +22,32 @@ com.glines.socketio socketio-core + + com.glines.socketio.extension + socketio-jetty + org.codehaus.jettison jettison + + + log4j + log4j + + + org.slf4j + slf4j-log4j12 + + + org.slf4j + slf4j-api + + - org.testatoo.container - testatoo-container-jetty + org.mortbay.jetty + servlet-api provided diff --git a/samples/eventbus/src/main/java/com/glines/socketio/sample/eventbus/EventBusServlet.java b/samples/eventbus/src/main/java/com/glines/socketio/sample/eventbus/EventBusServlet.java index 52299cb..5a17759 100644 --- a/samples/eventbus/src/main/java/com/glines/socketio/sample/eventbus/EventBusServlet.java +++ b/samples/eventbus/src/main/java/com/glines/socketio/sample/eventbus/EventBusServlet.java @@ -2,6 +2,8 @@ * The MIT License * Copyright (c) 2010 Tad Glines * + * Contributors: Ovea.com, Mycila.com + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights @@ -27,38 +29,36 @@ import com.glines.socketio.server.SocketIOInbound; import com.glines.socketio.server.SocketIOOutbound; import com.glines.socketio.server.SocketIOServlet; +import com.glines.socketio.util.JdkOverLog4j; import org.codehaus.jettison.json.JSONArray; import org.codehaus.jettison.json.JSONException; import org.codehaus.jettison.json.JSONObject; -import org.eclipse.jetty.util.log.JavaUtilLog; -import org.eclipse.jetty.util.log.Log; +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; -import java.io.IOException; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.logging.Level; -import java.util.logging.LogManager; import java.util.logging.Logger; public class EventBusServlet extends SocketIOServlet { - static { - try { - Log.setLog(new JavaUtilLog()); - LogManager.getLogManager().reset(); - LogManager.getLogManager().readConfiguration(Thread.currentThread().getContextClassLoader().getResourceAsStream("logging.properties")); - } catch (IOException e) { - throw new RuntimeException(e.getMessage(), e); - } - } - private static final Logger LOGGER = Logger.getLogger(EventBusServlet.class.getName()); + private static final long serialVersionUID = 5761989518185277878L; private final ConcurrentMap subscriptions = new ConcurrentHashMap(); + @Override + public void init(ServletConfig config) throws ServletException { + JdkOverLog4j.install(); + super.init(config); + } + @Override protected SocketIOInbound doSocketIOConnect(HttpServletRequest request) { + if (LOGGER.isLoggable(Level.FINE)) + LOGGER.fine("doSocketIOConnect : " + request.getRemoteHost() + ":" + request.getRemotePort()); return new Endpoint(request.getSession().getId(), request.getRemoteHost(), request.getRemotePort()); } diff --git a/samples/eventbus/src/main/resources/log4j.xml b/samples/eventbus/src/main/resources/log4j.xml new file mode 100644 index 0000000..d6530cf --- /dev/null +++ b/samples/eventbus/src/main/resources/log4j.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/eventbus/src/main/webapp/WEB-INF/web.xml b/samples/eventbus/src/main/webapp/WEB-INF/web.xml index 4685661..583648b 100644 --- a/samples/eventbus/src/main/webapp/WEB-INF/web.xml +++ b/samples/eventbus/src/main/webapp/WEB-INF/web.xml @@ -4,6 +4,8 @@ The MIT License Copyright (c) 2010 Tad Glines + Contributors: Ovea.com, Mycila.com + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights diff --git a/samples/eventbus/src/main/webapp/index.html b/samples/eventbus/src/main/webapp/index.html index 0561a98..f762407 100644 --- a/samples/eventbus/src/main/webapp/index.html +++ b/samples/eventbus/src/main/webapp/index.html @@ -3,6 +3,8 @@ The MIT License Copyright (c) 2010 Tad Glines + Contributors: Ovea.com, Mycila.com + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights @@ -31,103 +33,179 @@ + + + + +Topic: +Message: +

In the Firebug console, you can execute:

TESTER.start();
TESTER.subscribe('myTopic');
TESTER.publish('myTopic', 'myMessage');
TESTER.stop();

+

 
 
diff --git a/samples/eventbus/src/test/java/StartSampleEventBus.java b/samples/eventbus/src/test/java/StartSampleEventBus.java
index a23b91d..4c21d78 100644
--- a/samples/eventbus/src/test/java/StartSampleEventBus.java
+++ b/samples/eventbus/src/test/java/StartSampleEventBus.java
@@ -2,6 +2,8 @@
  * The MIT License
  * Copyright (c) 2010 Tad Glines
  *
+ * Contributors: Ovea.com, Mycila.com
+ *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
  * in the Software without restriction, including without limitation the rights
diff --git a/samples/pom.xml b/samples/pom.xml
index b032f47..234a594 100644
--- a/samples/pom.xml
+++ b/samples/pom.xml
@@ -26,4 +26,33 @@
         chat-gwt
     
 
+    
+
+        
+            jetty
+            
+                true
+            
+            
+                
+                    org.testatoo.container
+                    testatoo-container-jetty
+                    test
+                
+            
+        
+
+        
+            tomcat
+            
+                
+                    org.testatoo.container
+                    testatoo-container-tomcat
+                    test
+                
+            
+        
+
+    
+