/* * Copyright 2013 The Netty Project * * The Netty Project licenses this file to you under the Apache License, version * 2.0 (the "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package org.jboss.aerogear.io.netty.handler.codec.sockjs.handler; import io.netty.channel.Channel; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.HttpRequest; import io.netty.util.ReferenceCountUtil; import io.netty.util.internal.logging.InternalLogger; import io.netty.util.internal.logging.InternalLoggerFactory; import org.jboss.aerogear.io.netty.handler.codec.sockjs.SockJsSessionContext; import org.jboss.aerogear.io.netty.handler.codec.sockjs.handler.SessionState.State; import org.jboss.aerogear.io.netty.handler.codec.sockjs.protocol.CloseFrame; import org.jboss.aerogear.io.netty.handler.codec.sockjs.protocol.MessageFrame; import org.jboss.aerogear.io.netty.handler.codec.sockjs.protocol.OpenFrame; import org.jboss.aerogear.io.netty.handler.codec.sockjs.util.ArgumentUtil; /** * A ChannelHandler that manages SockJS sessions. * * For every connection received a new SessionHandler will be created and added to * the pipeline. * * Depending on the type of connection (polling, streaming, send, or websocket) * the type of {@link SessionState} that this session handles will differ. * */ public class SessionHandler extends ChannelHandlerAdapter { public enum Event { CLOSE_SESSION, HANDLE_SESSION } private static final InternalLogger logger = InternalLoggerFactory.getInstance(SessionHandler.class); private final SessionState sessionState; public SessionHandler(final SessionState sessionState) { ArgumentUtil.checkNotNull(sessionState, "sessionState"); this.sessionState = sessionState; } @Override public void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception { if (msg instanceof HttpRequest) { if (logger.isDebugEnabled()) { logger.debug("Handle session : {}", sessionState); } ReferenceCountUtil.release(msg); handleSession(ctx); } else if (msg instanceof String) { handleMessage((String) msg); } else { ctx.fireChannelRead(ReferenceCountUtil.retain(msg)); } } private void handleSession(final ChannelHandlerContext ctx) throws Exception { switch (sessionState.getState()) { case CONNECTING: sessionConnecting(ctx); break; case OPEN: sessionOpen(ctx); break; case INTERRUPTED: sessionInterrupted(ctx); break; case CLOSED: sessionClosed(ctx); break; } } private void sessionConnecting(final ChannelHandlerContext ctx) { logger.debug("State.CONNECTING sending open frame"); writeOpenFrame(ctx); sessionState.onConnect(ctx, new DefaultSockJsSessionContext()); } private void sessionOpen(ChannelHandlerContext ctx) { if (sessionState.isInUse()) { if (logger.isDebugEnabled()) { logger.debug("Another connection still in open for {}", sessionState); } writeCloseFrame(ctx, 2010, "Another connection still open"); sessionState.setState(State.INTERRUPTED); } else { sessionState.onOpen(ctx); } } private void sessionInterrupted(ChannelHandlerContext ctx) { writeCloseFrame(ctx, 1002, "Connection interrupted"); } private void sessionClosed(ChannelHandlerContext ctx) { writeCloseFrame(ctx, 3000, "Go away!"); sessionState.onClose(); } private void handleMessage(final String message) throws Exception { sessionState.onMessage(message); } @Override public void channelInactive(final ChannelHandlerContext ctx) throws Exception { sessionState.resetInuse(); ctx.fireChannelInactive(); } private static boolean isWritable(final Channel channel) { return channel.isActive() && channel.isRegistered(); } @Override public void userEventTriggered(final ChannelHandlerContext ctx, final Object event) throws Exception { if (event == Event.CLOSE_SESSION) { sessionState.onClose(); sessionState.onSockJSServerInitiatedClose(); } else if (event == Event.HANDLE_SESSION) { handleSession(ctx); } } private static void writeCloseFrame(final ChannelHandlerContext ctx, final int code, final String message) { ctx.channel().writeAndFlush(new CloseFrame(code, message)); } private static void writeOpenFrame(final ChannelHandlerContext ctx) { ctx.channel().writeAndFlush(new OpenFrame()); } public class DefaultSockJsSessionContext implements SockJsSessionContext { @Override public void send(String message) { final Channel channel = sessionState.getSendingContext().channel(); if (isWritable(channel)) { channel.writeAndFlush(new MessageFrame(message)); } else { sessionState.storeMessage(message); } } @Override public void close() { sessionState.onClose(); final Channel channel = sessionState.getSendingContext().channel(); if (isWritable(channel)) { final CloseFrame closeFrame = new CloseFrame(3000, "Go away!"); if (logger.isDebugEnabled()) { logger.debug("Writing {}", closeFrame); } channel.writeAndFlush(closeFrame).addListener(ChannelFutureListener.CLOSE); } } @Override public ChannelHandlerContext getContext() { return sessionState.getSendingContext(); } } }