jun.flume.netty.http.HTTPSource.java Source code

Java tutorial

Introduction

Here is the source code for jun.flume.netty.http.HTTPSource.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF 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
 *
 *     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 jun.flume.netty.http;

import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import org.apache.flume.ChannelException;
import org.apache.flume.Context;
import org.apache.flume.Event;
import org.apache.flume.EventDrivenSource;
import org.apache.flume.conf.Configurable;
import org.apache.flume.instrumentation.SourceCounter;
import org.apache.flume.source.AbstractSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.codec.http.HttpVersion;

import java.io.UnsupportedEncodingException;
import java.util.*;

public class HTTPSource extends AbstractSource implements EventDrivenSource, Configurable {

    private static final Logger LOG = LoggerFactory.getLogger(HTTPSource.class);
    private volatile Integer port;
    private volatile String host;
    private volatile Integer nettyHttpObjectAggregatorMaxLength;
    private volatile ServerBootstrap srv;
    private volatile ChannelFuture f;
    private volatile EventLoopGroup bossGroup;
    private volatile EventLoopGroup workerGroup;
    private volatile Thread t;
    private volatile String charset;
    private NettyHTTPSourceHandler handler;
    private SourceCounter sourceCounter;

    public void configure(Context context) {
        try {
            LOG.info("configing... ");
            port = context.getInteger(HTTPSourceConfigurationConstants.CONFIG_PORT);
            host = context.getString(HTTPSourceConfigurationConstants.CONFIG_BIND,
                    HTTPSourceConfigurationConstants.DEFAULT_BIND);
            checkHostAndPort();
            nettyHttpObjectAggregatorMaxLength = context.getInteger(
                    HTTPSourceConfigurationConstants.CONFIG_MAXLENGTH,
                    HTTPSourceConfigurationConstants.DEFAULT_MAXLENGTH);
            charset = context.getString(HTTPSourceConfigurationConstants.CONFIG_CHARSET,
                    HTTPSourceConfigurationConstants.DEFAULT_CHARSET);
            Preconditions.checkState(host != null && !host.isEmpty(), "HTTPSource hostname specified is empty");
            Preconditions.checkNotNull(port, "HTTPSource requires a port number to be" + " specified");

            String handlerClassName = context.getString(HTTPSourceConfigurationConstants.CONFIG_HANDLER,
                    HTTPSourceConfigurationConstants.DEFAULT_HANDLER).trim();
            @SuppressWarnings("unchecked")
            Class<? extends NettyHTTPSourceHandler> clazz = (Class<? extends NettyHTTPSourceHandler>) Class
                    .forName(handlerClassName);
            handler = clazz.getDeclaredConstructor().newInstance();
            Map<String, String> subProps = context
                    .getSubProperties(HTTPSourceConfigurationConstants.CONFIG_HANDLER_PREFIX);
            handler.configure(new Context(subProps));
            LOG.info("configed... ");
        } catch (ClassCastException ex) {
            LOG.error("Deserializer is not an instance of HTTPSourceHandler."
                    + "Deserializer must implement HTTPSourceHandler.");
            Throwables.propagate(ex);
        } catch (Exception ex) {
            LOG.error("Error configuring HTTPSource!", ex);
            Throwables.propagate(ex);
        }
        if (sourceCounter == null) {
            sourceCounter = new SourceCounter(getName());
        }
    }

    private void checkHostAndPort() {
        Preconditions.checkState(host != null && !host.isEmpty(), "HTTPSource hostname specified is empty");
        Preconditions.checkNotNull(port, "HTTPSource requires a port number to be" + " specified");
    }

    @Override
    public void start() {
        Preconditions.checkState(srv == null, "Running HTTP Server found in source: " + getName()
                + " before I started one." + "Will not attempt to start.");
        bossGroup = new NioEventLoopGroup();
        workerGroup = new NioEventLoopGroup();
        LOG.info("HTTP Server in source:" + getName() + " starting...");
        srv = new ServerBootstrap();

        try {
            srv.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new HttpRequestDecoder());
                            ch.pipeline().addLast(new HttpObjectAggregator(nettyHttpObjectAggregatorMaxLength));
                            ch.pipeline().addLast(new HttpResponseEncoder());
                            ch.pipeline().addLast(new ResponseHandler());
                        }
                    }).option(ChannelOption.SO_BACKLOG, 128).childOption(ChannelOption.SO_KEEPALIVE, true);

            t = new Thread() {
                @Override
                public void run() {
                    try {
                        f = srv.bind(host, port).sync();
                        f.channel().closeFuture().sync();
                    } catch (InterruptedException ex) {
                        LOG.warn("Thread interrupted... ");
                    } catch (Exception ex) {
                        LOG.error("Error while starting HTTPSource. Exception follows.", ex);
                        Throwables.propagate(ex);
                    } finally {
                        workerGroup.shutdownGracefully();
                        bossGroup.shutdownGracefully();
                    }
                }
            };

            t.start();
            LOG.info("HTTP Server in source:" + getName() + " started...");
        } catch (Exception ex) {
            LOG.error("Error while starting HTTPSource. Exception follows.", ex);
            Throwables.propagate(ex);
        }
        sourceCounter.start();
        super.start();
    }

    @Override
    public void stop() {
        try {
            LOG.debug("stoping... ");
            t.interrupt();
            workerGroup = null;
            bossGroup = null;
            srv = null;
            LOG.debug("stoped...");
        } catch (Exception ex) {
            LOG.error("Error while stopping HTTPSource. Exception follows.", ex);
        }
        sourceCounter.stop();
        LOG.info("Http source {} stopped. Metrics: {}", getName(), sourceCounter);
    }

    private class ResponseHandler extends ChannelInboundHandlerAdapter implements Configurable {
        private HttpRequest request;
        private final Gson gson;

        public ResponseHandler() {
            super();
            gson = new GsonBuilder().create();
        }

        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            if (msg instanceof HttpRequest) {
                request = (HttpRequest) msg;
            }
            if (msg instanceof HttpContent) {
                HttpContent content = (HttpContent) msg;
                ByteBuf buf = content.content();
                String rev = getMessage(buf);
                LOG.info("{ResponseHandler}:message received: " + rev + ". message length: " + rev.length());

                HttpResponseStatus repStatus = HttpResponseStatus.SERVICE_UNAVAILABLE;

                List<Event> events = Collections.emptyList();
                events = handler.getEvents(rev);
                HashMap<String, String> ht = new HashMap<String, String>();
                String res;
                try {

                    sourceCounter.incrementAppendBatchReceivedCount();
                    sourceCounter.addToEventReceivedCount(events.size());

                    getChannelProcessor().processEventBatch(events);

                    ht.put("result", "OK");
                    ht.put("massage_number", Integer.toString(events.size()));

                    sourceCounter.incrementAppendBatchAcceptedCount();
                    sourceCounter.addToEventAcceptedCount(events.size());

                } catch (ChannelException ex) {
                    LOG.warn("Error appending event to channel. "
                            + "Channel might be full. Consider increasing the channel "
                            + "capacity or make sure the sinks perform faster.", ex);
                    repStatus = HttpResponseStatus.INTERNAL_SERVER_ERROR;
                    ht.put("error", "Error appending event to channel. Channel might be full." + ex.getMessage());
                    return;
                } catch (Exception ex) {
                    LOG.warn("Unexpected error appending event to channel. ", ex);
                    repStatus = HttpResponseStatus.BAD_REQUEST;
                    ht.put("error", "Unexpected error while appending event to channel. " + ex.getMessage());
                    return;
                } finally {
                    buf.release();
                    res = gson.toJson(ht);
                    FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, repStatus,
                            Unpooled.wrappedBuffer(res.getBytes("utf-8")));
                    response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain");
                    response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());

                    if (HttpUtil.isKeepAlive(request)) {
                        response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
                    }
                    ctx.write(response);
                    ctx.flush();
                }

            }
        }

        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
            ctx.flush();
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            LOG.error(cause.getMessage(), cause);
            ctx.close();
        }

        private String getMessage(ByteBuf buf) {
            byte[] con = new byte[buf.readableBytes()];
            buf.readBytes(con);
            try {
                return new String(con, charset);
            } catch (UnsupportedEncodingException e) {
                LOG.error(e.getMessage(), e);
                return null;
            }
        }

        public void configure(Context context) {
            // TODO Auto-generated method stub

        }
    }

}