com.srotya.sidewinder.core.ingress.http.HTTPDataPointDecoder.java Source code

Java tutorial

Introduction

Here is the source code for com.srotya.sidewinder.core.ingress.http.HTTPDataPointDecoder.java

Source

/**
 * Copyright 2017 Ambud Sharma
 * 
 * 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.srotya.sidewinder.core.ingress.http;

import static io.netty.handler.codec.http.HttpHeaderNames.CONNECTION;
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH;
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
import static io.netty.handler.codec.http.HttpResponseStatus.CONTINUE;
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Logger;

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.srotya.sidewinder.core.storage.DataPoint;
import com.srotya.sidewinder.core.storage.StorageEngine;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
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.HttpHeaderValues;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.util.CharsetUtil;

/**
 * HTTP Protocol follows an InfluxDB wire format for ease of use with clients.
 * 
 * References:
 * https://netty.io/4.0/xref/io/netty/example/http/snoop/HttpSnoopServerHandler.html
 * 
 * @author ambud
 */
public class HTTPDataPointDecoder extends SimpleChannelInboundHandler<Object> {

    private static final Logger logger = Logger.getLogger(HTTPDataPointDecoder.class.getName());
    private StringBuilder responseString = new StringBuilder();
    private HttpRequest request;
    private StorageEngine engine;
    private String dbName;
    private String path;
    private StringBuilder requestBuffer = new StringBuilder();

    public HTTPDataPointDecoder(StorageEngine engine) {
        this.engine = engine;
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        try {
            if (msg instanceof HttpRequest) {
                HttpRequest request = this.request = (HttpRequest) msg;
                if (HttpUtil.is100ContinueExpected(request)) {
                    send100Continue(ctx);
                }

                QueryStringDecoder queryStringDecoder = new QueryStringDecoder(request.uri());
                path = queryStringDecoder.path();

                Map<String, List<String>> params = queryStringDecoder.parameters();
                if (!params.isEmpty()) {
                    for (Entry<String, List<String>> p : params.entrySet()) {
                        String key = p.getKey();
                        if (key.equalsIgnoreCase("db")) {
                            dbName = p.getValue().get(0);
                        }
                    }
                }

                if (path != null && path.contains("query")) {
                    Gson gson = new Gson();
                    JsonObject obj = new JsonObject();
                    JsonArray ary = new JsonArray();
                    ary.add(new JsonObject());
                    obj.add("results", ary);
                    responseString.append(gson.toJson(obj));
                }
            }

            if (msg instanceof HttpContent) {
                HttpContent httpContent = (HttpContent) msg;
                ByteBuf byteBuf = httpContent.content();
                if (byteBuf.isReadable()) {
                    requestBuffer.append(byteBuf.toString(CharsetUtil.UTF_8));
                }

                if (msg instanceof LastHttpContent) {
                    // LastHttpContent lastHttpContent = (LastHttpContent) msg;
                    // if (!lastHttpContent.trailingHeaders().isEmpty()) {
                    // }

                    if (dbName == null) {
                        responseString.append("Invalid database null");
                        logger.severe("Invalid database null");
                    } else {
                        String payload = requestBuffer.toString();
                        logger.fine("Request:" + payload);
                        List<DataPoint> dps = dataPointsFromString(dbName, payload);
                        for (DataPoint dp : dps) {
                            try {
                                engine.writeDataPoint(dp);
                                logger.fine("Accepted:" + dp + "\t" + new Date(dp.getTimestamp()));
                            } catch (IOException e) {
                                logger.fine("Dropped:" + dp + "\t" + e.getMessage());
                                responseString.append("Dropped:" + dp);
                            }
                        }
                    }
                    if (writeResponse(request, ctx)) {
                        ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
    }

    public static List<DataPoint> dataPointsFromString(String dbName, String payload) {
        List<DataPoint> dps = new ArrayList<>();
        String[] splits = payload.split("[\\r\\n]+");
        for (String split : splits) {
            try {
                String[] parts = split.split("\\s+");
                if (parts.length < 2 || parts.length > 3) {
                    // invalid datapoint => drop
                    continue;
                }
                long timestamp = System.currentTimeMillis();
                if (parts.length == 3) {
                    timestamp = Long.parseLong(parts[2]) / (1000 * 1000);
                }
                String[] key = parts[0].split(",");
                String measurementName = key[0];
                List<String> tags = new ArrayList<>();
                for (int i = 1; i < key.length; i++) {
                    tags.add(key[i]);
                }
                String[] fields = parts[1].split(",");
                for (String field : fields) {
                    String[] fv = field.split("=");
                    String valueFieldName = fv[0];
                    if (fv[1].contains(".")) {
                        double value = Double.parseDouble(fv[1]);
                        DataPoint dp = new DataPoint(dbName, measurementName, valueFieldName, tags, timestamp,
                                value);
                        dp.setFp(true);
                        dps.add(dp);
                    } else {
                        if (fv[1].endsWith("i")) {
                            fv[1] = fv[1].substring(0, fv[1].length() - 1);
                        }
                        long value = Long.parseLong(fv[1]);
                        DataPoint dp = new DataPoint(dbName, measurementName, valueFieldName, tags, timestamp,
                                value);
                        dp.setFp(false);
                        dps.add(dp);
                    }
                }
            } catch (Exception e) {
                logger.fine("Rejected:" + split);
            }
        }
        return dps;
    }

    private boolean writeResponse(HttpObject httpObject, ChannelHandlerContext ctx) {
        FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1,
                httpObject.decoderResult().isSuccess() ? OK : BAD_REQUEST,
                Unpooled.copiedBuffer(responseString.toString().toString(), CharsetUtil.UTF_8));
        response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8");

        response.headers().set(CONTENT_LENGTH, response.content().readableBytes());
        response.headers().set(CONNECTION, HttpHeaderValues.KEEP_ALIVE);

        responseString = new StringBuilder();
        // Write the response.
        ctx.write(response);
        return true;
    }

    private static void send100Continue(ChannelHandlerContext ctx) {
        FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, CONTINUE);
        ctx.write(response);
    }
}