All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jboss.netty.handler.codec.frame.FrameDecoder Maven / Gradle / Ivy

There is a newer version: 0.10.0
Show newest version
/*
 * Copyright 2012 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.netty.handler.codec.frame;

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBufferFactory;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.buffer.CompositeChannelBuffer;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandler;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.ChannelUpstreamHandler;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.LifeCycleAwareChannelHandler;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
import org.jboss.netty.handler.codec.replay.ReplayingDecoder;

import java.net.SocketAddress;

/**
 * Decodes the received {@link ChannelBuffer}s into a meaningful frame object.
 * 

* In a stream-based transport such as TCP/IP, packets can be fragmented and * reassembled during transmission even in a LAN environment. For example, * let us assume you have received three packets: *

 * +-----+-----+-----+
 * | ABC | DEF | GHI |
 * +-----+-----+-----+
 * 
* because of the packet fragmentation, a server can receive them like the * following: *
 * +----+-------+---+---+
 * | AB | CDEFG | H | I |
 * +----+-------+---+---+
 * 
*

* {@link FrameDecoder} helps you defrag the received packets into one or more * meaningful frames that could be easily understood by the * application logic. In case of the example above, your {@link FrameDecoder} * implementation could defrag the received packets like the following: *

 * +-----+-----+-----+
 * | ABC | DEF | GHI |
 * +-----+-----+-----+
 * 
*

* The following code shows an example handler which decodes a frame whose * first 4 bytes header represents the length of the frame, excluding the * header. *

 * MESSAGE FORMAT
 * ==============
 *
 * Offset:  0        4                   (Length + 4)
 *          +--------+------------------------+
 * Fields:  | Length | Actual message content |
 *          +--------+------------------------+
 *
 * DECODER IMPLEMENTATION
 * ======================
 *
 * public class IntegerHeaderFrameDecoder extends {@link FrameDecoder} {
 *
 *   {@code @Override}
 *   protected Object decode({@link ChannelHandlerContext} ctx,
 *                           {@link Channel channel},
 *                           {@link ChannelBuffer} buf) throws Exception {
 *
 *     // Make sure if the length field was received.
 *     if (buf.readableBytes() < 4) {
 *        // The length field was not received yet - return null.
 *        // This method will be invoked again when more packets are
 *        // received and appended to the buffer.
 *        return null;
 *     }
 *
 *     // The length field is in the buffer.
 *
 *     // Mark the current buffer position before reading the length field
 *     // because the whole frame might not be in the buffer yet.
 *     // We will reset the buffer position to the marked position if
 *     // there's not enough bytes in the buffer.
 *     buf.markReaderIndex();
 *
 *     // Read the length field.
 *     int length = buf.readInt();
 *
 *     // Make sure if there's enough bytes in the buffer.
 *     if (buf.readableBytes() < length) {
 *        // The whole bytes were not received yet - return null.
 *        // This method will be invoked again when more packets are
 *        // received and appended to the buffer.
 *
 *        // Reset to the marked position to read the length field again
 *        // next time.
 *        buf.resetReaderIndex();
 *
 *        return null;
 *     }
 *
 *     // There's enough bytes in the buffer. Read it.
 *     {@link ChannelBuffer} frame = buf.readBytes(length);
 *
 *     // Successfully decoded a frame.  Return the decoded frame.
 *     return frame;
 *   }
 * }
 * 
* *

Returning a POJO rather than a {@link ChannelBuffer}

*

* Please note that you can return an object of a different type than * {@link ChannelBuffer} in your {@code decode()} and {@code decodeLast()} * implementation. For example, you could return a * POJO so that the next * {@link ChannelUpstreamHandler} receives a {@link MessageEvent} which * contains a POJO rather than a {@link ChannelBuffer}. * *

Replacing a decoder with another decoder in a pipeline

*

* If you are going to write a protocol multiplexer, you will probably want to * replace a {@link FrameDecoder} (protocol detector) with another * {@link FrameDecoder} or {@link ReplayingDecoder} (actual protocol decoder). * It is not possible to achieve this simply by calling * {@link ChannelPipeline#replace(ChannelHandler, String, ChannelHandler)}, but * some additional steps are required: *

 * public class FirstDecoder extends {@link FrameDecoder} {
 *
 *     public FirstDecoder() {
 *         super(true); // Enable unfold
 *     }
 *
 *     {@code @Override}
 *     protected Object decode({@link ChannelHandlerContext} ctx,
 *                             {@link Channel} channel,
 *                             {@link ChannelBuffer} buf) {
 *         ...
 *         // Decode the first message
 *         Object firstMessage = ...;
 *
 *         // Add the second decoder
 *         ctx.getPipeline().addLast("second", new SecondDecoder());
 *
 *         // Remove the first decoder (me)
 *         ctx.getPipeline().remove(this);
 *
 *         if (buf.readable()) {
 *             // Hand off the remaining data to the second decoder
 *             return new Object[] { firstMessage, buf.readBytes(buf.readableBytes()) };
 *         } else {
 *             // Nothing to hand off
 *             return firstMessage;
 *         }
 *     }
 * }
 * 
* * @apiviz.landmark */ public abstract class FrameDecoder extends SimpleChannelUpstreamHandler implements LifeCycleAwareChannelHandler { public static final int DEFAULT_MAX_COMPOSITEBUFFER_COMPONENTS = 1024; private boolean unfold; protected ChannelBuffer cumulation; private volatile ChannelHandlerContext ctx; private int copyThreshold; private int maxCumulationBufferComponents = DEFAULT_MAX_COMPOSITEBUFFER_COMPONENTS; protected FrameDecoder() { this(false); } protected FrameDecoder(boolean unfold) { this.unfold = unfold; } public final boolean isUnfold() { return unfold; } public final void setUnfold(boolean unfold) { if (ctx == null) { this.unfold = unfold; } else { throw new IllegalStateException( "decoder properties cannot be changed once the decoder is added to a pipeline."); } } /** * See {@link #setMaxCumulationBufferCapacity(int)} for explaintation of this setting * */ public final int getMaxCumulationBufferCapacity() { return copyThreshold; } /** * Set the maximal capacity of the internal cumulation ChannelBuffer to use * before the {@link FrameDecoder} tries to minimize the memory usage by * "byte copy". * * * What you use here really depends on your application and need. Using * {@link Integer#MAX_VALUE} will disable all byte copies but give you the * cost of a higher memory usage if big {@link ChannelBuffer}'s will be * received. * * By default a threshold of {@code 0} is used, which means it will * always copy to try to reduce memory usage * * * @param copyThreshold * the threshold (in bytes) or {@link Integer#MAX_VALUE} to * disable it. The value must be at least 0 * @throws IllegalStateException * get thrown if someone tries to change this setting after the * Decoder was added to the {@link ChannelPipeline} */ public final void setMaxCumulationBufferCapacity(int copyThreshold) { if (copyThreshold < 0) { throw new IllegalArgumentException("maxCumulationBufferCapacity must be >= 0"); } if (ctx == null) { this.copyThreshold = copyThreshold; } else { throw new IllegalStateException( "decoder properties cannot be changed once the decoder is added to a pipeline."); } } /** * Returns the maximum number of components in the cumulation buffer. If the number of * the components in the cumulation buffer exceeds this value, the components of the * cumulation buffer are consolidated into a single component, involving memory copies. * The default value of this property {@link #DEFAULT_MAX_COMPOSITEBUFFER_COMPONENTS}. */ public final int getMaxCumulationBufferComponents() { return maxCumulationBufferComponents; } /** * Sets the maximum number of components in the cumulation buffer. If the number of * the components in the cumulation buffer exceeds this value, the components of the * cumulation buffer are consolidated into a single component, involving memory copies. * The default value of this property is {@link #DEFAULT_MAX_COMPOSITEBUFFER_COMPONENTS} * and its minimum allowed value is {@code 2}. */ public final void setMaxCumulationBufferComponents(int maxCumulationBufferComponents) { if (maxCumulationBufferComponents < 2) { throw new IllegalArgumentException( "maxCumulationBufferComponents: " + maxCumulationBufferComponents + " (expected: >= 2)"); } if (ctx == null) { this.maxCumulationBufferComponents = maxCumulationBufferComponents; } else { throw new IllegalStateException( "decoder properties cannot be changed once the decoder is added to a pipeline."); } } @Override public void messageReceived( ChannelHandlerContext ctx, MessageEvent e) throws Exception { Object m = e.getMessage(); if (!(m instanceof ChannelBuffer)) { ctx.sendUpstream(e); return; } ChannelBuffer input = (ChannelBuffer) m; if (!input.readable()) { return; } if (cumulation == null) { try { // the cumulation buffer is not created yet so just pass the input to callDecode(...) method callDecode(ctx, e.getChannel(), input, e.getRemoteAddress()); } finally { updateCumulation(ctx, input); } } else { input = appendToCumulation(input); try { callDecode(ctx, e.getChannel(), input, e.getRemoteAddress()); } finally { updateCumulation(ctx, input); } } } protected ChannelBuffer appendToCumulation(ChannelBuffer input) { ChannelBuffer cumulation = this.cumulation; assert cumulation.readable(); if (cumulation instanceof CompositeChannelBuffer) { // Make sure the resulting cumulation buffer has no more than the configured components. CompositeChannelBuffer composite = (CompositeChannelBuffer) cumulation; if (composite.numComponents() >= maxCumulationBufferComponents) { cumulation = composite.copy(); } } this.cumulation = input = ChannelBuffers.wrappedBuffer(cumulation, input); return input; } protected ChannelBuffer updateCumulation(ChannelHandlerContext ctx, ChannelBuffer input) { ChannelBuffer newCumulation; int readableBytes = input.readableBytes(); if (readableBytes > 0) { int inputCapacity = input.capacity(); // If input.readableBytes() == input.capacity() (i.e. input is full), // there's nothing to save from creating a new cumulation buffer // even if input.capacity() exceeds the threshold, because the new cumulation // buffer will have the same capacity and content with input. if (readableBytes < inputCapacity && inputCapacity > copyThreshold) { // At least one byte was consumed by callDecode() and input.capacity() // exceeded the threshold. cumulation = newCumulation = newCumulationBuffer(ctx, input.readableBytes()); cumulation.writeBytes(input); } else { // Nothing was consumed by callDecode() or input.capacity() did not // exceed the threshold. if (input.readerIndex() != 0) { cumulation = newCumulation = input.slice(); } else { cumulation = newCumulation = input; } } } else { cumulation = newCumulation = null; } return newCumulation; } @Override public void channelDisconnected( ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { cleanup(ctx, e); } @Override public void channelClosed( ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { cleanup(ctx, e); } @Override public void exceptionCaught( ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { ctx.sendUpstream(e); } /** * Decodes the received packets so far into a frame. * * If an sub-class wants to extract a frame out of the buffer it should use * the {@link #extractFrame(ChannelBuffer, int, int)} method, * to make optimizations easier later. * * @param ctx the context of this handler * @param channel the current channel * @param buffer the cumulative buffer of received packets so far. * Note that the buffer might be empty, which means you * should not make an assumption that the buffer contains * at least one byte in your decoder implementation. * * @return the decoded frame if a full frame was received and decoded. * {@code null} if there's not enough data in the buffer to decode a frame. */ protected abstract Object decode( ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) throws Exception; /** * Decodes the received data so far into a frame when the channel is * disconnected. * * @param ctx the context of this handler * @param channel the current channel * @param buffer the cumulative buffer of received packets so far. * Note that the buffer might be empty, which means you * should not make an assumption that the buffer contains * at least one byte in your decoder implementation. * * @return the decoded frame if a full frame was received and decoded. * {@code null} if there's not enough data in the buffer to decode a frame. */ protected Object decodeLast( ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) throws Exception { return decode(ctx, channel, buffer); } private void callDecode( ChannelHandlerContext context, Channel channel, ChannelBuffer cumulation, SocketAddress remoteAddress) throws Exception { while (cumulation.readable()) { int oldReaderIndex = cumulation.readerIndex(); Object frame = decode(context, channel, cumulation); if (frame == null) { if (oldReaderIndex == cumulation.readerIndex()) { // Seems like more data is required. // Let us wait for the next notification. break; } else { // Previous data has been discarded. // Probably it is reading on. continue; } } if (oldReaderIndex == cumulation.readerIndex()) { throw new IllegalStateException( "decode() method must read at least one byte " + "if it returned a frame (caused by: " + getClass() + ')'); } unfoldAndFireMessageReceived(context, remoteAddress, frame); } } protected final void unfoldAndFireMessageReceived( ChannelHandlerContext context, SocketAddress remoteAddress, Object result) { if (unfold) { if (result instanceof Object[]) { for (Object r: (Object[]) result) { Channels.fireMessageReceived(context, r, remoteAddress); } } else if (result instanceof Iterable) { for (Object r: (Iterable) result) { Channels.fireMessageReceived(context, r, remoteAddress); } } else { Channels.fireMessageReceived(context, result, remoteAddress); } } else { Channels.fireMessageReceived(context, result, remoteAddress); } } /** * Gets called on {@link #channelDisconnected(ChannelHandlerContext, ChannelStateEvent)} and * {@link #channelClosed(ChannelHandlerContext, ChannelStateEvent)} */ protected void cleanup(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { try { ChannelBuffer cumulation = this.cumulation; if (cumulation == null) { return; } this.cumulation = null; if (cumulation.readable()) { // Make sure all frames are read before notifying a closed channel. callDecode(ctx, ctx.getChannel(), cumulation, null); } // Call decodeLast() finally. Please note that decodeLast() is // called even if there's nothing more to read from the buffer to // notify a user that the connection was closed explicitly. Object partialFrame = decodeLast(ctx, ctx.getChannel(), cumulation); if (partialFrame != null) { unfoldAndFireMessageReceived(ctx, null, partialFrame); } } finally { ctx.sendUpstream(e); } } /** * Create a new {@link ChannelBuffer} which is used for the cumulation. * Sub-classes may override this. * * @param ctx {@link ChannelHandlerContext} for this handler * @return buffer the {@link ChannelBuffer} which is used for cumulation */ protected ChannelBuffer newCumulationBuffer( ChannelHandlerContext ctx, int minimumCapacity) { ChannelBufferFactory factory = ctx.getChannel().getConfig().getBufferFactory(); return factory.getBuffer(Math.max(minimumCapacity, 256)); } /** * Replace this {@link FrameDecoder} in the {@link ChannelPipeline} with the given {@link ChannelHandler}. All * remaining bytes in the {@link ChannelBuffer} will get send to the new {@link ChannelHandler} that was used * as replacement * */ public void replace(String handlerName, ChannelHandler handler) { if (ctx == null) { throw new IllegalStateException( "Replace cann only be called once the FrameDecoder is added to the ChannelPipeline"); } ChannelPipeline pipeline = ctx.getPipeline(); pipeline.addAfter(ctx.getName(), handlerName, handler); try { if (cumulation != null) { Channels.fireMessageReceived(ctx, cumulation.readBytes(actualReadableBytes())); } } finally { pipeline.remove(this); } } /** * Returns the actual number of readable bytes in the internal cumulative * buffer of this decoder. You usually do not need to rely on this value * to write a decoder. Use it only when you muse use it at your own risk. * This method is a shortcut to {@link #internalBuffer() internalBuffer().readableBytes()}. */ protected int actualReadableBytes() { return internalBuffer().readableBytes(); } /** * Returns the internal cumulative buffer of this decoder. You usually * do not need to access the internal buffer directly to write a decoder. * Use it only when you must use it at your own risk. */ protected ChannelBuffer internalBuffer() { ChannelBuffer buf = cumulation; if (buf == null) { return ChannelBuffers.EMPTY_BUFFER; } return buf; } /** * Extract a Frame of the specified buffer. By default this implementation * will return a extract the sub-region of the buffer and create a new one. * If an sub-class want to extract a frame from the buffer it should use this method * by default. * * Be sure that this method MUST not modify the readerIndex of the given buffer * */ protected ChannelBuffer extractFrame(ChannelBuffer buffer, int index, int length) { ChannelBuffer frame = buffer.factory().getBuffer(length); frame.writeBytes(buffer, index, length); return frame; } public void beforeAdd(ChannelHandlerContext ctx) throws Exception { this.ctx = ctx; } public void afterAdd(ChannelHandlerContext ctx) throws Exception { // Nothing to do.. } public void beforeRemove(ChannelHandlerContext ctx) throws Exception { // Nothing to do.. } public void afterRemove(ChannelHandlerContext ctx) throws Exception { // Nothing to do.. } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy