package cc.blynk.server.handlers; import cc.blynk.server.Limits; import cc.blynk.server.core.protocol.exceptions.QuotaLimitException; import cc.blynk.server.core.protocol.handlers.DefaultExceptionHandler; import cc.blynk.server.core.protocol.model.messages.MessageBase; import cc.blynk.server.core.session.StateHolderBase; import cc.blynk.server.core.stats.metrics.InstanceLoadMeter; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.util.ReferenceCountUtil; /** * The Blynk Project. * Created by Dmitriy Dumanskiy. * Created on 2/3/2015. */ public abstract class BaseSimpleChannelInboundHandler<I> extends ChannelInboundHandlerAdapter implements DefaultExceptionHandler { /* * in case of consistent quota limit exceed during long term, sending warning response back to exceeding channel * for performance reason sending only 1 message within interval. In millis * * this property was never changed, so moving it to static field */ private final static int USER_QUOTA_LIMIT_WARN_PERIOD = 60_000; private final int USER_QUOTA_LIMIT; private final Class<?> type; private final InstanceLoadMeter quotaMeter; private long lastQuotaExceededTime; protected BaseSimpleChannelInboundHandler(Class<?> type, Limits limits) { this.type = type; this.USER_QUOTA_LIMIT = limits.USER_QUOTA_LIMIT; this.quotaMeter = new InstanceLoadMeter(); } private static int getMsgId(Object o) { if (o instanceof MessageBase) { return ((MessageBase) o).id; } return 0; } @Override @SuppressWarnings("unchecked") public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if (type.isInstance(msg)) { try { if (quotaMeter.getOneMinuteRate() > USER_QUOTA_LIMIT) { sendErrorResponseIfTicked(); return; } quotaMeter.mark(); messageReceived(ctx, (I) msg); } catch (Exception e) { handleGeneralException(ctx, e, getMsgId(msg)); } finally { ReferenceCountUtil.release(msg); } } } private void sendErrorResponseIfTicked() { long now = System.currentTimeMillis(); //once a minute sending user response message in case limit is exceeded constantly if (lastQuotaExceededTime + USER_QUOTA_LIMIT_WARN_PERIOD < now) { lastQuotaExceededTime = now; throw new QuotaLimitException("User has exceeded message quota limit."); } } /** * <strong>Please keep in mind that this method will be renamed to * {@code messageReceived(ChannelHandlerContext, I)} in 5.0.</strong> * * Is called for each message of type {@link I}. * * @param ctx the {@link ChannelHandlerContext} which this {@link SimpleChannelInboundHandler} * belongs to * @param msg the message to handle */ public abstract void messageReceived(ChannelHandlerContext ctx, I msg); public abstract StateHolderBase getState(); public InstanceLoadMeter getQuotaMeter() { return quotaMeter; } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { handleGeneralException(ctx, cause); } }