/* * Copyright 2013 The Solmix Project * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.gnu.org/licenses/ * or see the FSF site: http://www.fsf.org. */ package org.solmix.ipc.avro; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPromise; import io.netty.handler.codec.ByteToMessageDecoder; import io.netty.handler.codec.MessageToByteEncoder; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import org.apache.avro.AvroRuntimeException; /** * Data structure, encoder and decoder classes for the Netty transport. adapte netty 5.0 * * @author solmix.f@gmail.com * @version $Id$ 2014年2月16日 */ public class MyNettyCodec { /** * Transport protocol data structure when using Netty. */ public static class NettyDataPack { private int serial; // to track each call in client side private List<ByteBuffer> datas; public NettyDataPack() { } public NettyDataPack(int serial, List<ByteBuffer> datas) { this.serial = serial; this.datas = datas; } public void setSerial(int serial) { this.serial = serial; } public int getSerial() { return serial; } public void setDatas(List<ByteBuffer> datas) { this.datas = datas; } public List<ByteBuffer> getDatas() { return datas; } } /** * Protocol encoder which converts NettyDataPack which contains the Responder's output List<ByteBuffer> to * ByteBuf needed by Netty. */ public static class NettyFrameEncoder extends MessageToByteEncoder<NettyDataPack> { /** * {@inheritDoc} * * @see io.netty.handler.codec.MessageToByteEncoder#encode(io.netty.channel.ChannelHandlerContext, * java.lang.Object, io.netty.buffer.ByteBuf) */ @Override protected void encode(ChannelHandlerContext ctx, NettyDataPack dataPack, ByteBuf out) throws Exception { List<ByteBuffer> origs = dataPack.getDatas(); out.writeBytes(getPackHeader(dataPack)); // prepend a pack header including serial number and list size for (ByteBuffer b : origs) { out.writeBytes(getLengthHeader(b));// for each buffer prepend length field out.writeBytes(b); } } @Override public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { super.write(ctx, msg, promise); ctx.flush(); } private ByteBuffer getLengthHeader(ByteBuffer buf) { ByteBuffer header = ByteBuffer.allocate(4); header.putInt(buf.limit()); header.flip(); return header; } private ByteBuffer getPackHeader(NettyDataPack dataPack) { ByteBuffer header = ByteBuffer.allocate(8); header.putInt(dataPack.getSerial()); header.putInt(dataPack.getDatas().size()); header.flip(); return header; } } /** * Protocol decoder which converts Netty's ByteBuf to NettyDataPack which contains a List<ByteBuffer> needed * by Avro Responder. */ public static class NettyFrameDecoder extends ByteToMessageDecoder { private boolean packHeaderRead = false; private int listSize; private NettyDataPack dataPack; private final long maxMem; private static final long SIZEOF_REF = 8L; // mem usage of 64-bit pointer public NettyFrameDecoder() { maxMem = Runtime.getRuntime().maxMemory(); } /** * {@inheritDoc} * * @see io.netty.handler.codec.ByteToMessageDecoder#decode(io.netty.channel.ChannelHandlerContext, * io.netty.buffer.ByteBuf, java.util.List) */ @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { if (!packHeaderRead) { if (decodePackHeader(ctx, in)) { packHeaderRead = true; } return; } else { if (decodePackBody(ctx, in)) { packHeaderRead = false; // reset state out.add(dataPack); } else { return; } } } private boolean decodePackBody(ChannelHandlerContext ctx, ByteBuf in) throws Exception{ if (in.readableBytes() < 4) { return false; } in.markReaderIndex(); int length = in.readInt(); if (in.readableBytes() < length) { in.resetReaderIndex(); return false; } ByteBuffer bb = ByteBuffer.allocate(length); in.readBytes(bb); bb.flip(); dataPack.getDatas().add(bb); return dataPack.getDatas().size()==listSize; } private boolean decodePackHeader(ChannelHandlerContext ctx, ByteBuf in)throws Exception { if (in.readableBytes()<8) { return false; } int serial = in.readInt(); int listSize = in.readInt(); // Sanity check to reduce likelihood of invalid requests being honored. // Only allow 10% of available memory to go towards this list (too much!) if (listSize * SIZEOF_REF > 0.1 * maxMem) { ctx.channel().close().await(); throw new AvroRuntimeException("Excessively large list allocation " + "request detected: " + listSize + " items! Connection closed."); } this.listSize = listSize; dataPack = new NettyDataPack(serial, new ArrayList<ByteBuffer>(listSize)); return true; } } }