如何将字符串插入Netty CompositeByteBuf的中间

时间:2017-06-21 13:53:19

标签: java netty

我有一个CompositeByteBuf,其中包含一些包含HTTP请求的缓冲区,我想在HTTP请求行之后注入一个额外的HTTP头字段。 (我不想使用整个HTTP编码器/解码器,因为我只是代理数据而不需要将所有数据解析为HTTP)。

如何使用派生缓冲区执行此操作,从而避免复制CompositeByteBuf的内容。我使用slice和readSlice进行的每次尝试都产生了indexoutofbounds错误或Stack Overflow。任何人都可以建议替代以下不需要复制整个compositebytebuf?

/**
 * Injects an XFF header into pendingBuf 
 */
private void addXForwardedForHeaderToPendingBuf(
                 int pLFpos, 
              String pRemoteIPaddr)
{
    //create a new buffer 
    ByteBuf newBuf = inboundChannel.alloc().directBuffer(); 

    //add the HTTP request line to it
    ByteBufUtil.writeUtf8(newBuf, 
                          pendingBuf.readCharSequence(pLFpos + 1, 
                          CharsetUtil.UTF_8));

    //add the XFF header
    ByteBufUtil.writeUtf8(newBuf, "X-Forwarded-For: ");
    ByteBufUtil.writeUtf8(newBuf, pRemoteIPaddr);
    ByteBufUtil.writeUtf8(newBuf, "\r\n");

    //add anything from the original buffer that came after the request line
    int bytesRemaining = pendingBuf.readableBytes();
    if (bytesRemaining > 0)
    {
        newBuf.writeBytes(pendingBuf);
    }

    //clear pendingBuf
    pendingBuf.removeComponents(0, pendingBuf.numComponents()); 
    pendingBuf.setIndex(0, 0);

    //add newBuf into pendingBuf 
    pendingBuf.addComponent(newBuf);
    pendingBuf.writerIndex(pendingBuf.writerIndex() + newBuf.writerIndex()); 
}

1 个答案:

答案 0 :(得分:1)

编辑当前的bytebuf时有一个缺点,即在最坏的情况下需要移动所有字节,我们可以利用CompositeByteBuf具有我们可以根据需要编辑和移动的组件这一事实。

我们基本上想要实现以下步骤:

  1. 由于Bytebuf内可能有多个CompositeByteBuf,我们想要搜索要修改的buf的索引。

    ByteBuf为我们提供了以下方法:

    遗憾的是,在字符串末尾插入的情况下,这些方法无法正常工作,因为这在技术上超出了原始缓冲区的范围,我们需要为此添加一个特殊情况。

  2. 我们希望实现我们想要在多个缓冲区之间的边界上准确插入的特殊情况,因为在这些情况下我们实际上可以使用零拷贝。

  3. 如果拆分索引碰巧落在bytebuf的中间,我们需要将其拆分,并将其自身添加为2个独立的缓冲区。
  4. 我们需要更新复合词for some reason this doesn't happen by default.
  5. 上的编写器索引

    使用上面的流程,我们可以创建以下代码:

    public static void insertString(CompositeByteBuf buffer, int index, ByteBuf insertion) {
        try {
            if (buffer == null) {
                throw new NullPointerException("buffer");
            }
            if (insertion == null) {
                throw new NullPointerException("insertion");
            }
            if (buffer.readableBytes() < index) {
                throw new IllegalArgumentException("buffer.readableBytes() < index: "
                        + buffer.readableBytes() + " < " + index);
            }
    
            // Start by checking the offset where we need to inject the insertion
            int injectionBufOffset;
            int injectionByteOffset;
            if (index == buffer.readableBytes()) {
                injectionBufOffset = buffer.numComponents();
                injectionByteOffset = 0;
            } else {
                injectionBufOffset = buffer.toComponentIndex(index);
                injectionByteOffset = index - buffer.toByteIndex(injectionBufOffset);
            }
    
            // Optimalize in the case of offset 0
            if (injectionByteOffset == 0) {
                buffer.addComponent(injectionBufOffset, insertion.retain());
                buffer.writerIndex(buffer.writerIndex() + insertion.readableBytes());
                return;
            }
            // Do the split technique
            ByteBuf toSplit = buffer.internalComponent(injectionBufOffset).retain();
            try {
                buffer.removeComponent(injectionBufOffset);
                buffer.addComponent(injectionBufOffset + 0,
                        toSplit.readSlice(injectionByteOffset).retain());
                buffer.addComponent(injectionBufOffset + 1, insertion.retain());
                buffer.addComponent(injectionBufOffset + 2,
                        toSplit.retain());
                buffer.writerIndex(buffer.writerIndex() + insertion.readableBytes());
            } finally {
                ReferenceCountUtil.release(toSplit);
            }
        } finally {
            if (insertion != null) {
                ReferenceCountUtil.release(insertion);
            }
        }
    }
    

    由于此代码非常复杂,我们还希望确保其测试正确,因此,我们需要一些单元测试(JUnit):

    import static test.NettySplit.insertString;
    
    public class NettySplitTest {
    
        CompositeByteBuf buffer;
        ByteBuf test;
    
        private void addByteBuf(CompositeByteBuf target, ByteBuf source) {
            target.addComponent(source);
            target.writerIndex(target.writerIndex() + source.readableBytes());
        }
    
        @Before
        public void before() {
            buffer = ByteBufAllocator.DEFAULT.compositeBuffer();
        }
    
        @After
        public void after() {
            ReferenceCountUtil.release(buffer);
            buffer = null;
            ReferenceCountUtil.release(test);
            test = null;
        }
    
        @Test
        public void testSplitting() {
            addByteBuf(buffer, Unpooled.wrappedBuffer(new byte[]{0, 1, 2, 3}));
    
            insertString(buffer, 2, Unpooled.wrappedBuffer(new byte[]{5}));
    
            test = Unpooled.wrappedBuffer(new byte[]{0, 1, 5, 2, 3});
            assertEquals(test, buffer);
    
        }
    
        @Test
        public void testInsertionStart() {
            addByteBuf(buffer, Unpooled.wrappedBuffer(new byte[]{0, 1, 2, 3}));
    
            insertString(buffer, 0, Unpooled.wrappedBuffer(new byte[]{5}));
    
            test = Unpooled.wrappedBuffer(new byte[]{5, 0, 1, 2, 3});
            assertEquals(test, buffer);
        }
    
        @Test
        public void testInsertionEnd() {
            addByteBuf(buffer, Unpooled.wrappedBuffer(new byte[]{0, 1, 2, 3}));
    
            insertString(buffer, 4, Unpooled.wrappedBuffer(new byte[]{5}));
    
            test = Unpooled.wrappedBuffer(new byte[]{0, 1, 2, 3, 5});
            assertEquals(test, buffer);
        }
    
        @Test
        public void testInsertionSplitEnd() {
            addByteBuf(buffer, Unpooled.wrappedBuffer(new byte[]{0, 1, 2, 3}));
            addByteBuf(buffer, Unpooled.wrappedBuffer(new byte[]{0, 1, 2, 3}));
    
            insertString(buffer, 6, Unpooled.wrappedBuffer(new byte[]{5}));
    
            test = Unpooled.wrappedBuffer(new byte[]{0, 1, 2, 3, 0, 1, 5, 2, 3});
            assertEquals(test, buffer);
        }
    
    }