/**
* Copyright 2012 Ronen Hamias, Anton Kharenko
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package io.scalecube.socketio.pipeline;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageEncoder;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.CharsetUtil;
import io.netty.util.ReferenceCounted;
import io.scalecube.socketio.TransportType;
import io.scalecube.socketio.packets.IPacket;
import io.scalecube.socketio.packets.Packet;
import io.scalecube.socketio.packets.PacketsFrame;
import io.scalecube.socketio.serialization.PacketEncoder;
import io.scalecube.socketio.serialization.PacketFramer;
/**
* <p>
* This class encodes Socket.IO messages they're sent. The structure of a
* message is as follows:
* </p>
*
* <p>
* {@code [message type] ':' [message id ('+')] ':' [message endpoint] (':' [message data])}
* </p>
*
* <p>
* The message type is a single digit integer. The message id is an incremental
* integer, required for ACKs (can be ommitted). If the message id is followed
* by a +, the ACK is not handled by socket.io, but by the user instead.
* Socket.IO has built-in support for multiple channels of communication (which
* we call "multiple sockets"). Each socket is identified by an endpoint (can be
* omitted).
* </p>
*
* @author Ronen Hamias, Anton Kharenko
*
*/
@ChannelHandler.Sharable
public class PacketEncoderHandler extends MessageToMessageEncoder<Object> {
private final static String JSONP_TEMPLATE = "io.j[%s]('%s');";
private final Logger log = LoggerFactory.getLogger(getClass());
@Override
protected void encode(ChannelHandlerContext ctx, Object msg, List<Object> out) throws Exception {
if (msg instanceof IPacket) {
IPacket packet = (IPacket) msg;
if (log.isDebugEnabled())
log.debug("Sending packet: {} to channel: {}", msg, ctx.channel());
ByteBuf encodedPacket = encodePacket(packet);
if (log.isDebugEnabled())
log.debug("Encoded packet: {}", encodedPacket);
TransportType transportType = packet.getTransportType();
if (transportType == TransportType.WEBSOCKET || transportType == TransportType.FLASHSOCKET) {
out.add(new TextWebSocketFrame(encodedPacket));
} else if (transportType == TransportType.XHR_POLLING) {
out.add(PipelineUtils.createHttpResponse(packet.getOrigin(), encodedPacket, false));
} else if (transportType == TransportType.JSONP_POLLING) {
String jsonpIndexParam = (packet.getJsonpIndexParam() != null) ? packet.getJsonpIndexParam() : "0";
String encodedStringPacket = encodedPacket.toString(CharsetUtil.UTF_8);
encodedPacket.release();
String encodedJsonpPacket = String.format(JSONP_TEMPLATE, jsonpIndexParam, encodedStringPacket);
HttpResponse httpResponse = PipelineUtils.createHttpResponse(packet.getOrigin(), PipelineUtils.copiedBuffer(ctx.alloc(), encodedJsonpPacket), true);
httpResponse.headers().add("X-XSS-Protection", "0");
out.add(httpResponse);
} else {
throw new UnsupportedTransportTypeException(transportType);
}
} else {
if (msg instanceof ReferenceCounted) {
((ReferenceCounted) msg).retain();
}
out.add(msg);
}
}
private ByteBuf encodePacket(final IPacket msg) throws Exception {
if (msg instanceof PacketsFrame) {
return PacketFramer.encodePacketsFrame((PacketsFrame) msg);
} else if (msg instanceof Packet) {
return PacketEncoder.encodePacket((Packet) msg);
} else {
throw new UnsupportedPacketTypeException(msg);
}
}
}