com.heliosapm.streams.onramp.PipelineFactory.java Source code

Java tutorial

Introduction

Here is the source code for com.heliosapm.streams.onramp.PipelineFactory.java

Source

/*
 * Copyright 2015 the original author or authors.
 *
 * Licensed 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 com.heliosapm.streams.onramp;

import java.nio.charset.Charset;
import java.util.Properties;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import com.heliosapm.utils.config.ConfigurationHelper;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.http.HttpContentDecompressor;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.json.JsonObjectDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.HashedWheelTimer;
import io.netty.util.Timer;

/**
 * <p>Title: PipelineFactory</p>
 * <p>Description: Factory to create the netty pipeline</p> 
 * <p>Company: Helios Development Group LLC</p>
 * @author Whitehead (nwhitehead AT heliosdev DOT org)
 * <p><code>com.heliosapm.streams.onramp.PipelineFactory</code></p>
 */

/**
 * Creates a newly configured {@link ChannelPipeline} for a new channel.
 * This class is supposed to be a singleton.
 */
@ChannelHandler.Sharable
public final class PipelineFactory extends ChannelInitializer<SocketChannel> implements ThreadFactory {
    /** The UTF8 character set */
    public static final Charset UTF8 = Charset.forName("UTF8");
    /** A UTF8 String encoder */
    private static final StringEncoder ENCODER = new StringEncoder(UTF8);
    /** Connection tracker */
    private final ConnectionManager connmgr = ConnectionManager.getInstance();
    /** Protocol detector */
    private final DetectHttpOrRpc HTTP_OR_RPC = new DetectHttpOrRpc();
    /** Idle Channel timeout handler timer */
    private final Timer timer = new HashedWheelTimer(this, 100L, TimeUnit.MILLISECONDS, 512);
    /** Thread serial factory */
    private static final AtomicInteger threadSerial = new AtomicInteger(0);

    /** Stateless handler for RPCs. */
    private final TextLineRpcHandler rpchandler;

    /** The idle channel reaper */
    private final IdleSessionKillerHandler idleReaper;
    /** The instance logger */
    protected final Logger log = LogManager.getLogger(getClass());
    /** The http-json rpc handler */
    protected final HttpJsonRpcHandler jsonRpcHandler;

    /** The server side socket timeout. **/
    private final int socketTimeout;

    /**
     * Creates a new pipeline factory
     * @param appConfig The optional config properties
     */
    public PipelineFactory(final Properties appConfig) {
        socketTimeout = ConfigurationHelper.getIntSystemThenEnvProperty("onramp.socket.timeout", 60, appConfig);
        rpchandler = new TextLineRpcHandler();
        idleReaper = new IdleSessionKillerHandler();
        final String metricTopic = ConfigurationHelper.getSystemThenEnvProperty("onramp.metric.topic",
                "tsdb.metrics.binary", appConfig);
        final String metaTopic = ConfigurationHelper.getSystemThenEnvProperty("onramp.meta.topic",
                "tsdb.meta.binary", appConfig);
        final int batchSize = ConfigurationHelper.getIntSystemThenEnvProperty("onramp.metric.batchsize", 1024,
                appConfig);
        jsonRpcHandler = new HttpJsonRpcHandler(metricTopic, metaTopic, batchSize);
    }

    /**
     * {@inheritDoc}
     * @see java.util.concurrent.ThreadFactory#newThread(java.lang.Runnable)
     */
    @Override
    public Thread newThread(Runnable r) {
        final Thread t = new Thread(r, "PipelineTimer#" + threadSerial.incrementAndGet());
        t.setDaemon(true);
        return t;
    }

    private static boolean isHttp(int magic1, int magic2) {
        return magic1 == 'G' && magic2 == 'E' || // GET
                magic1 == 'P' && magic2 == 'O' || // POST
                magic1 == 'P' && magic2 == 'U' || // PUT
                magic1 == 'H' && magic2 == 'E' || // HEAD
                magic1 == 'O' && magic2 == 'P' || // OPTIONS
                magic1 == 'P' && magic2 == 'A' || // PATCH
                magic1 == 'D' && magic2 == 'E' || // DELETE
                magic1 == 'T' && magic2 == 'R' || // TRACE
                magic1 == 'C' && magic2 == 'O'; // CONNECT
    }

    /**
     * {@inheritDoc}
     * @see io.netty.channel.ChannelInitializer#initChannel(io.netty.channel.Channel)
     */
    @Override
    protected void initChannel(final SocketChannel ch) throws Exception {
        final ChannelPipeline pipeline = ch.pipeline();
        pipeline.addLast("idlestate", new IdleStateHandler(0, 0, 120)); // this.socketTimeout
        //      pipeline.addLast("idlereaper", idleReaper);
        pipeline.addLast("connmgr", connmgr);
        pipeline.addLast("gzipdetector", new GZipDetector());
        pipeline.addLast("detect", HTTP_OR_RPC);
    }

    /**
     * Dynamically changes the {@link ChannelPipeline} based on the request.
     * If a request uses HTTP, then this changes the pipeline to process HTTP.
     * Otherwise, the pipeline is changed to processes an RPC.
     */
    @ChannelHandler.Sharable
    final class DetectHttpOrRpc extends SimpleChannelInboundHandler<ByteBuf> {
        private final Logger log = LogManager.getLogger(getClass());

        @Override
        protected void channelRead0(final ChannelHandlerContext ctx, final ByteBuf in) throws Exception {

            if (in.readableBytes() < 5) {
                in.retain();
                return;
            }
            try {
                log.info("Detecting Http vs. Telnet. Pipeline is {}", ctx.pipeline().names());
                final int magic1 = in.getUnsignedByte(in.readerIndex());
                final int magic2 = in.getUnsignedByte(in.readerIndex() + 1);
                if (isHttp(magic1, magic2)) {
                    log.debug("Switching to Http [{}]", ctx.channel());
                    switchToHttp(ctx, 512 * 1024); //tsdb.getConfig().max_chunked_requests());
                } else {
                    log.debug("Switching to Telnet [{}]", ctx.channel());
                    switchToTelnet(ctx);
                }
                //         ctx.pipeline().remove(this);
                in.retain();
                log.info("Sending [{}] up pipeline {}", in, ctx.pipeline().names());
                ctx.fireChannelRead(in);
            } finally {
                //            ReferenceCountUtil.releaseLater(in);
            }
        }

        private void switchToTelnet(final ChannelHandlerContext ctx) {
            ChannelPipeline p = ctx.pipeline();
            p.addLast("framer", new LineBasedFrameDecoder(1024, true, true));
            p.addLast("linehandler", new TextLineRpcHandler());
            p.remove(this);
        }

        /**
         * Modifies the pipeline to handle HTTP requests
         * @param ctx The calling channel handler context
         * @param maxRequestSize The maximum request size in bytes
         */
        private void switchToHttp(final ChannelHandlerContext ctx, final int maxRequestSize) {
            ChannelPipeline p = ctx.pipeline();
            p.addLast("httpHandler", new HttpServerCodec()); // TODO: config ?
            p.addLast("decompressor", new HttpContentDecompressor());
            p.addLast("aggregator", new HttpObjectAggregator(maxRequestSize));
            p.addLast("jsonDecoder", new JsonObjectDecoder(maxRequestSize, false));
            p.addLast("handler", jsonRpcHandler);
            p.remove(this);
        }
    }

    /**
     * <p>Title: IdleSessionKillerHandler</p>
     * <p>Description: Handler to close a channel when it has idled for longer than <b><code>onramp.socket.timeout</code></b> seconds.</p> 
     * <p>Company: Helios Development Group LLC</p>
     * @author Whitehead (nwhitehead AT heliosdev DOT org)
     * <p><code>com.heliosapm.streams.onramp.PipelineFactory.IdleSessionKillerHandler</code></p>
     */
    @ChannelHandler.Sharable
    protected class IdleSessionKillerHandler extends ChannelDuplexHandler {
        /**
         * {@inheritDoc}
         * @see io.netty.channel.ChannelInboundHandlerAdapter#userEventTriggered(io.netty.channel.ChannelHandlerContext, java.lang.Object)
         */
        @Override
        public void userEventTriggered(final ChannelHandlerContext ctx, final Object evt) throws Exception {
            if (evt instanceof IdleStateEvent) {
                ctx.close();
            }
        }
    }
}