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

io.netty.handler.codec.ByteToMessageDecoder Maven / Gradle / Ivy

Go to download

This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up with different versions on classes on the class path).

There is a newer version: 34.0.0.Final
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:
 *
 *   https://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.netty.handler.codec;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.CompositeByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelConfig;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.socket.ChannelInputShutdownEvent;
import io.netty.util.IllegalReferenceCountException;
import io.netty.util.internal.ObjectUtil;
import io.netty.util.internal.StringUtil;

import java.util.List;

import static io.netty.util.internal.ObjectUtil.checkPositive;
import static java.lang.Integer.MAX_VALUE;

/**
 * {@link ChannelInboundHandlerAdapter} which decodes bytes in a stream-like fashion from one {@link ByteBuf} to an
 * other Message type.
 *
 * For example here is an implementation which reads all readable bytes from
 * the input {@link ByteBuf} and create a new {@link ByteBuf}.
 *
 * 
 *     public class SquareDecoder extends {@link ByteToMessageDecoder} {
 *         {@code @Override}
 *         public void decode({@link ChannelHandlerContext} ctx, {@link ByteBuf} in, List<Object> out)
 *                 throws {@link Exception} {
 *             out.add(in.readBytes(in.readableBytes()));
 *         }
 *     }
 * 
* *

Frame detection

*

* Generally frame detection should be handled earlier in the pipeline by adding a * {@link DelimiterBasedFrameDecoder}, {@link FixedLengthFrameDecoder}, {@link LengthFieldBasedFrameDecoder}, * or {@link LineBasedFrameDecoder}. *

* If a custom frame decoder is required, then one needs to be careful when implementing * one with {@link ByteToMessageDecoder}. Ensure there are enough bytes in the buffer for a * complete frame by checking {@link ByteBuf#readableBytes()}. If there are not enough bytes * for a complete frame, return without modifying the reader index to allow more bytes to arrive. *

* To check for complete frames without modifying the reader index, use methods like {@link ByteBuf#getInt(int)}. * One MUST use the reader index when using methods like {@link ByteBuf#getInt(int)}. * For example calling in.getInt(0) is assuming the frame starts at the beginning of the buffer, which * is not always the case. Use in.getInt(in.readerIndex()) instead. *

Pitfalls

*

* Be aware that sub-classes of {@link ByteToMessageDecoder} MUST NOT * annotated with {@link @Sharable}. *

* Some methods such as {@link ByteBuf#readBytes(int)} will cause a memory leak if the returned buffer * is not released or added to the out {@link List}. Use derived buffers like {@link ByteBuf#readSlice(int)} * to avoid leaking memory. */ public abstract class ByteToMessageDecoder extends ChannelInboundHandlerAdapter { /** * Cumulate {@link ByteBuf}s by merge them into one {@link ByteBuf}'s, using memory copies. */ public static final Cumulator MERGE_CUMULATOR = new Cumulator() { @Override public ByteBuf cumulate(ByteBufAllocator alloc, ByteBuf cumulation, ByteBuf in) { if (cumulation == in) { // when the in buffer is the same as the cumulation it is doubly retained, release it once in.release(); return cumulation; } if (!cumulation.isReadable() && in.isContiguous()) { // If cumulation is empty and input buffer is contiguous, use it directly cumulation.release(); return in; } try { final int required = in.readableBytes(); if (required > cumulation.maxWritableBytes() || required > cumulation.maxFastWritableBytes() && cumulation.refCnt() > 1 || cumulation.isReadOnly()) { // Expand cumulation (by replacing it) under the following conditions: // - cumulation cannot be resized to accommodate the additional data // - cumulation can be expanded with a reallocation operation to accommodate but the buffer is // assumed to be shared (e.g. refCnt() > 1) and the reallocation may not be safe. return expandCumulation(alloc, cumulation, in); } cumulation.writeBytes(in, in.readerIndex(), required); in.readerIndex(in.writerIndex()); return cumulation; } finally { // We must release in all cases as otherwise it may produce a leak if writeBytes(...) throw // for whatever release (for example because of OutOfMemoryError) in.release(); } } }; /** * Cumulate {@link ByteBuf}s by add them to a {@link CompositeByteBuf} and so do no memory copy whenever possible. * Be aware that {@link CompositeByteBuf} use a more complex indexing implementation so depending on your use-case * and the decoder implementation this may be slower than just use the {@link #MERGE_CUMULATOR}. */ public static final Cumulator COMPOSITE_CUMULATOR = new Cumulator() { @Override public ByteBuf cumulate(ByteBufAllocator alloc, ByteBuf cumulation, ByteBuf in) { if (cumulation == in) { // when the in buffer is the same as the cumulation it is doubly retained, release it once in.release(); return cumulation; } if (!cumulation.isReadable()) { cumulation.release(); return in; } CompositeByteBuf composite = null; try { if (cumulation instanceof CompositeByteBuf && cumulation.refCnt() == 1) { composite = (CompositeByteBuf) cumulation; // Writer index must equal capacity if we are going to "write" // new components to the end if (composite.writerIndex() != composite.capacity()) { composite.capacity(composite.writerIndex()); } } else { composite = alloc.compositeBuffer(Integer.MAX_VALUE).addFlattenedComponents(true, cumulation); } composite.addFlattenedComponents(true, in); in = null; return composite; } finally { if (in != null) { // We must release if the ownership was not transferred as otherwise it may produce a leak in.release(); // Also release any new buffer allocated if we're not returning it if (composite != null && composite != cumulation) { composite.release(); } } } } }; private static final byte STATE_INIT = 0; private static final byte STATE_CALLING_CHILD_DECODE = 1; private static final byte STATE_HANDLER_REMOVED_PENDING = 2; ByteBuf cumulation; private Cumulator cumulator = MERGE_CUMULATOR; private boolean singleDecode; private boolean first; /** * This flag is used to determine if we need to call {@link ChannelHandlerContext#read()} to consume more data * when {@link ChannelConfig#isAutoRead()} is {@code false}. */ private boolean firedChannelRead; private boolean selfFiredChannelRead; /** * A bitmask where the bits are defined as *

    *
  • {@link #STATE_INIT}
  • *
  • {@link #STATE_CALLING_CHILD_DECODE}
  • *
  • {@link #STATE_HANDLER_REMOVED_PENDING}
  • *
*/ private byte decodeState = STATE_INIT; private int discardAfterReads = 16; private int numReads; protected ByteToMessageDecoder() { ensureNotSharable(); } /** * If set then only one message is decoded on each {@link #channelRead(ChannelHandlerContext, Object)} * call. This may be useful if you need to do some protocol upgrade and want to make sure nothing is mixed up. * * Default is {@code false} as this has performance impacts. */ public void setSingleDecode(boolean singleDecode) { this.singleDecode = singleDecode; } /** * If {@code true} then only one message is decoded on each * {@link #channelRead(ChannelHandlerContext, Object)} call. * * Default is {@code false} as this has performance impacts. */ public boolean isSingleDecode() { return singleDecode; } /** * Set the {@link Cumulator} to use for cumulate the received {@link ByteBuf}s. */ public void setCumulator(Cumulator cumulator) { this.cumulator = ObjectUtil.checkNotNull(cumulator, "cumulator"); } /** * Set the number of reads after which {@link ByteBuf#discardSomeReadBytes()} are called and so free up memory. * The default is {@code 16}. */ public void setDiscardAfterReads(int discardAfterReads) { checkPositive(discardAfterReads, "discardAfterReads"); this.discardAfterReads = discardAfterReads; } /** * 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 must 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 ByteBuf internalBuffer() { if (cumulation != null) { return cumulation; } else { return Unpooled.EMPTY_BUFFER; } } @Override public final void handlerRemoved(ChannelHandlerContext ctx) throws Exception { if (decodeState == STATE_CALLING_CHILD_DECODE) { decodeState = STATE_HANDLER_REMOVED_PENDING; return; } ByteBuf buf = cumulation; if (buf != null) { // Directly set this to null, so we are sure we not access it in any other method here anymore. cumulation = null; numReads = 0; int readable = buf.readableBytes(); if (readable > 0) { ctx.fireChannelRead(buf); ctx.fireChannelReadComplete(); } else { buf.release(); } } handlerRemoved0(ctx); } /** * Gets called after the {@link ByteToMessageDecoder} was removed from the actual context and it doesn't handle * events anymore. */ protected void handlerRemoved0(ChannelHandlerContext ctx) throws Exception { } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof ByteBuf) { selfFiredChannelRead = true; CodecOutputList out = CodecOutputList.newInstance(); try { first = cumulation == null; cumulation = cumulator.cumulate(ctx.alloc(), first ? Unpooled.EMPTY_BUFFER : cumulation, (ByteBuf) msg); callDecode(ctx, cumulation, out); } catch (DecoderException e) { throw e; } catch (Exception e) { throw new DecoderException(e); } finally { try { if (cumulation != null && !cumulation.isReadable()) { numReads = 0; try { cumulation.release(); } catch (IllegalReferenceCountException e) { //noinspection ThrowFromFinallyBlock throw new IllegalReferenceCountException( getClass().getSimpleName() + "#decode() might have released its input buffer, " + "or passed it down the pipeline without a retain() call, " + "which is not allowed.", e); } cumulation = null; } else if (++numReads >= discardAfterReads) { // We did enough reads already try to discard some bytes, so we not risk to see a OOME. // See https://github.com/netty/netty/issues/4275 numReads = 0; discardSomeReadBytes(); } int size = out.size(); firedChannelRead |= out.insertSinceRecycled(); fireChannelRead(ctx, out, size); } finally { out.recycle(); } } } else { ctx.fireChannelRead(msg); } } /** * Get {@code numElements} out of the {@link List} and forward these through the pipeline. */ static void fireChannelRead(ChannelHandlerContext ctx, List msgs, int numElements) { if (msgs instanceof CodecOutputList) { fireChannelRead(ctx, (CodecOutputList) msgs, numElements); } else { for (int i = 0; i < numElements; i++) { ctx.fireChannelRead(msgs.get(i)); } } } /** * Get {@code numElements} out of the {@link CodecOutputList} and forward these through the pipeline. */ static void fireChannelRead(ChannelHandlerContext ctx, CodecOutputList msgs, int numElements) { for (int i = 0; i < numElements; i ++) { ctx.fireChannelRead(msgs.getUnsafe(i)); } } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { numReads = 0; discardSomeReadBytes(); if (selfFiredChannelRead && !firedChannelRead && !ctx.channel().config().isAutoRead()) { ctx.read(); } firedChannelRead = false; selfFiredChannelRead = false; ctx.fireChannelReadComplete(); } protected final void discardSomeReadBytes() { if (cumulation != null && !first && cumulation.refCnt() == 1) { // discard some bytes if possible to make more room in the // buffer but only if the refCnt == 1 as otherwise the user may have // used slice().retain() or duplicate().retain(). // // See: // - https://github.com/netty/netty/issues/2327 // - https://github.com/netty/netty/issues/1764 cumulation.discardSomeReadBytes(); } } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { channelInputClosed(ctx, true); } @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt instanceof ChannelInputShutdownEvent) { // The decodeLast method is invoked when a channelInactive event is encountered. // This method is responsible for ending requests in some situations and must be called // when the input has been shutdown. channelInputClosed(ctx, false); } super.userEventTriggered(ctx, evt); } private void channelInputClosed(ChannelHandlerContext ctx, boolean callChannelInactive) { CodecOutputList out = CodecOutputList.newInstance(); try { channelInputClosed(ctx, out); } catch (DecoderException e) { throw e; } catch (Exception e) { throw new DecoderException(e); } finally { try { if (cumulation != null) { cumulation.release(); cumulation = null; } int size = out.size(); fireChannelRead(ctx, out, size); if (size > 0) { // Something was read, call fireChannelReadComplete() ctx.fireChannelReadComplete(); } if (callChannelInactive) { ctx.fireChannelInactive(); } } finally { // Recycle in all cases out.recycle(); } } } /** * Called when the input of the channel was closed which may be because it changed to inactive or because of * {@link ChannelInputShutdownEvent}. */ void channelInputClosed(ChannelHandlerContext ctx, List out) throws Exception { if (cumulation != null) { callDecode(ctx, cumulation, out); // If callDecode(...) removed the handle from the pipeline we should not call decodeLast(...) as this would // be unexpected. if (!ctx.isRemoved()) { // Use Unpooled.EMPTY_BUFFER if cumulation become null after calling callDecode(...). // See https://github.com/netty/netty/issues/10802. ByteBuf buffer = cumulation == null ? Unpooled.EMPTY_BUFFER : cumulation; decodeLast(ctx, buffer, out); } } else { decodeLast(ctx, Unpooled.EMPTY_BUFFER, out); } } /** * Called once data should be decoded from the given {@link ByteBuf}. This method will call * {@link #decode(ChannelHandlerContext, ByteBuf, List)} as long as decoding should take place. * * @param ctx the {@link ChannelHandlerContext} which this {@link ByteToMessageDecoder} belongs to * @param in the {@link ByteBuf} from which to read data * @param out the {@link List} to which decoded messages should be added */ protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List out) { try { while (in.isReadable()) { final int outSize = out.size(); if (outSize > 0) { fireChannelRead(ctx, out, outSize); out.clear(); // Check if this handler was removed before continuing with decoding. // If it was removed, it is not safe to continue to operate on the buffer. // // See: // - https://github.com/netty/netty/issues/4635 if (ctx.isRemoved()) { break; } } int oldInputLength = in.readableBytes(); decodeRemovalReentryProtection(ctx, in, out); // Check if this handler was removed before continuing the loop. // If it was removed, it is not safe to continue to operate on the buffer. // // See https://github.com/netty/netty/issues/1664 if (ctx.isRemoved()) { break; } if (out.isEmpty()) { if (oldInputLength == in.readableBytes()) { break; } else { continue; } } if (oldInputLength == in.readableBytes()) { throw new DecoderException( StringUtil.simpleClassName(getClass()) + ".decode() did not read anything but decoded a message."); } if (isSingleDecode()) { break; } } } catch (DecoderException e) { throw e; } catch (Exception cause) { throw new DecoderException(cause); } } /** * Decode the from one {@link ByteBuf} to an other. This method will be called till either the input * {@link ByteBuf} has nothing to read when return from this method or till nothing was read from the input * {@link ByteBuf}. * * @param ctx the {@link ChannelHandlerContext} which this {@link ByteToMessageDecoder} belongs to * @param in the {@link ByteBuf} from which to read data * @param out the {@link List} to which decoded messages should be added * @throws Exception is thrown if an error occurs */ protected abstract void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception; /** * Decode the from one {@link ByteBuf} to an other. This method will be called till either the input * {@link ByteBuf} has nothing to read when return from this method or till nothing was read from the input * {@link ByteBuf}. * * @param ctx the {@link ChannelHandlerContext} which this {@link ByteToMessageDecoder} belongs to * @param in the {@link ByteBuf} from which to read data * @param out the {@link List} to which decoded messages should be added * @throws Exception is thrown if an error occurs */ final void decodeRemovalReentryProtection(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { decodeState = STATE_CALLING_CHILD_DECODE; try { decode(ctx, in, out); } finally { boolean removePending = decodeState == STATE_HANDLER_REMOVED_PENDING; decodeState = STATE_INIT; if (removePending) { fireChannelRead(ctx, out, out.size()); out.clear(); handlerRemoved(ctx); } } } /** * Is called one last time when the {@link ChannelHandlerContext} goes in-active. Which means the * {@link #channelInactive(ChannelHandlerContext)} was triggered. * * By default, this will just call {@link #decode(ChannelHandlerContext, ByteBuf, List)} but sub-classes may * override this for some special cleanup operation. */ protected void decodeLast(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { if (in.isReadable()) { // Only call decode() if there is something left in the buffer to decode. // See https://github.com/netty/netty/issues/4386 decodeRemovalReentryProtection(ctx, in, out); } } static ByteBuf expandCumulation(ByteBufAllocator alloc, ByteBuf oldCumulation, ByteBuf in) { int oldBytes = oldCumulation.readableBytes(); int newBytes = in.readableBytes(); int totalBytes = oldBytes + newBytes; ByteBuf newCumulation = alloc.buffer(alloc.calculateNewCapacity(totalBytes, MAX_VALUE)); ByteBuf toRelease = newCumulation; try { // This avoids redundant checks and stack depth compared to calling writeBytes(...) newCumulation.setBytes(0, oldCumulation, oldCumulation.readerIndex(), oldBytes) .setBytes(oldBytes, in, in.readerIndex(), newBytes) .writerIndex(totalBytes); in.readerIndex(in.writerIndex()); toRelease = oldCumulation; return newCumulation; } finally { toRelease.release(); } } /** * Cumulate {@link ByteBuf}s. */ public interface Cumulator { /** * Cumulate the given {@link ByteBuf}s and return the {@link ByteBuf} that holds the cumulated bytes. * The implementation is responsible to correctly handle the life-cycle of the given {@link ByteBuf}s and so * call {@link ByteBuf#release()} if a {@link ByteBuf} is fully consumed. */ ByteBuf cumulate(ByteBufAllocator alloc, ByteBuf cumulation, ByteBuf in); } }