com.bunjlabs.fuga.network.netty.NettyHttpServerHandler.java Source code

Java tutorial

Introduction

Here is the source code for com.bunjlabs.fuga.network.netty.NettyHttpServerHandler.java

Source

/* 
 * 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.bunjlabs.fuga.network.netty;

import com.bunjlabs.fuga.FugaApp;
import com.bunjlabs.fuga.foundation.content.BufferedContent;
import com.bunjlabs.fuga.foundation.Cookie;
import com.bunjlabs.fuga.foundation.Request;
import com.bunjlabs.fuga.foundation.http.RequestMethod;
import com.bunjlabs.fuga.foundation.Response;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultHttpResponse;
import io.netty.handler.codec.http.DefaultLastHttpContent;
import io.netty.handler.codec.http.HttpChunkedInput;
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.HttpMethod;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.handler.codec.http.cookie.ServerCookieDecoder;
import io.netty.handler.codec.http.cookie.ServerCookieEncoder;
import io.netty.handler.stream.ChunkedStream;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

class NettyHttpServerHandler extends SimpleChannelInboundHandler<HttpObject> {

    private final Logger log = LogManager.getLogger(NettyHttpServerHandler.class);
    private final FugaApp app;
    private final String serverVersion;

    private final int forwarded;
    private ByteBuf contentBuffer;
    private HttpRequest httprequest;
    private Request.Builder requestBuilder;
    private boolean decoder;

    NettyHttpServerHandler(FugaApp app) {
        this.app = app;
        this.contentBuffer = Unpooled.buffer();

        this.serverVersion = app.getConfiguration().get("fuga.version");
        this.forwarded = app.getConfiguration().get("fuga.http.forwarded").equals("rfc7239") ? 1 : 0;
    }

    private void reset() {
        this.httprequest = null;
        this.requestBuilder = null;
        this.decoder = false;
        this.contentBuffer = Unpooled.buffer();
    }

    @Override
    public void channelRead0(ChannelHandlerContext ctx, HttpObject msg) {
        if (msg instanceof HttpRequest) {
            this.httprequest = (HttpRequest) msg;

            requestBuilder = new Request.Builder();

            requestBuilder.requestMethod(RequestMethod.valueOf(httprequest.method().name())).uri(httprequest.uri());

            try {

                // Decode URI GET query parameters
                QueryStringDecoder queryStringDecoder = new QueryStringDecoder(httprequest.uri());
                requestBuilder.path(queryStringDecoder.path()).query(queryStringDecoder.parameters());

                // Process cookies
                List<Cookie> cookies = new ArrayList<>();

                String cookieString = httprequest.headers().get(HttpHeaderNames.COOKIE);
                if (cookieString != null) {
                    ServerCookieDecoder.STRICT.decode(cookieString).stream().forEach((cookie) -> {
                        cookies.add(NettyCookieConverter.convertToFuga(cookie));
                    });
                }

                requestBuilder.cookies(cookies);

                // Process headers
                Map<String, String> headers = new HashMap<>();
                httprequest.headers().entries().stream().forEach((e) -> {
                    headers.put(e.getKey(), e.getValue());
                });

                requestBuilder.headers(headers);

                // Get real parameters from frontend HTTP server
                boolean isSecure = false;
                SocketAddress remoteAddress = ctx.channel().remoteAddress();

                if (forwarded == 1) { // RFC7239
                    if (headers.containsKey("Forwarded")) {
                        String fwdStr = headers.get("Forwarded");

                        List<String> fwdparams = Stream.of(fwdStr.split("; ")).map((s) -> s.trim())
                                .collect(Collectors.toList());

                        for (String f : fwdparams) {
                            String p[] = f.split("=");

                            switch (p[0]) {
                            case "for":
                                remoteAddress = parseAddress(p[1]);
                                break;
                            case "proto":
                                isSecure = p[1].equals("https");
                                break;
                            }
                        }
                    }
                } else if (forwarded == 0) { // X-Forwarded
                    if (headers.containsKey("X-Forwarded-Proto")) {
                        if (headers.get("X-Forwarded-Proto").equalsIgnoreCase("https")) {
                            isSecure = true;
                        }
                    }

                    if (headers.containsKey("X-Forwarded-For")) {
                        String fwdfor = headers.get("X-Forwarded-For");
                        remoteAddress = parseAddress(
                                fwdfor.contains(",") ? fwdfor.substring(0, fwdfor.indexOf(',')) : fwdfor);
                    } else if (headers.containsKey("X-Real-IP")) {
                        remoteAddress = parseAddress(headers.get("X-Real-IP"));
                    }
                }

                requestBuilder.remoteAddress(remoteAddress).isSecure(isSecure);

                if (headers.containsKey("Accept-Language")) {
                    String acceptLanguage = headers.get("Accept-Language");

                    List<Locale> acceptLocales = Stream.of(acceptLanguage.split(","))
                            .map((s) -> s.contains(";") ? s.substring(0, s.indexOf(";")).trim() : s.trim())
                            .map((s) -> s.contains("-") ? new Locale(s.split("-")[0], s.split("-")[0])
                                    : new Locale(s))
                            .collect(Collectors.toList());

                    requestBuilder.acceptLocales(acceptLocales);
                }
                //

                if (httprequest.method().equals(HttpMethod.GET)) {
                    processResponse(ctx);
                    return;
                }
                decoder = true;
            } catch (Exception e) {
                processClientError(ctx, requestBuilder.build(), 400);
                return;
            }
        }

        if (msg instanceof HttpContent && decoder) {
            HttpContent httpContent = (HttpContent) msg;

            contentBuffer.writeBytes(httpContent.content());

            if (httpContent instanceof LastHttpContent) {
                requestBuilder.content(new BufferedContent(contentBuffer.nioBuffer()));
                processResponse(ctx);
            }
        }
    }

    private void processClientError(ChannelHandlerContext ctx, Request request, int code) {
        Response response = app.getErrorHandler().onClientError(request, code);

        writeResponse(ctx, request, response);
        reset();
    }

    private void processServerError(ChannelHandlerContext ctx, Request request, Throwable cause) {
        Response response = app.getErrorHandler().onServerError(request, cause);

        writeResponse(ctx, request, response);
        reset();
    }

    private void processResponse(ChannelHandlerContext ctx) {
        Request request = requestBuilder.build();
        Response response = app.getRequestHandler().onRequest(request);

        if (response == null) {
            log.error("Null response is received");

            processServerError(ctx, request, new NullPointerException("Null response is received"));
            return;
        }

        writeResponse(ctx, request, response);
        reset();
    }

    private void writeResponse(ChannelHandlerContext ctx, Request request, Response response) {
        HttpResponse httpresponse = new DefaultHttpResponse(HttpVersion.HTTP_1_1,
                HttpResponseStatus.valueOf(response.status()));

        httpresponse.headers().set(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED);
        httpresponse.headers().set(HttpHeaderNames.CONTENT_TYPE, response.contentType());

        // Disable cache by default
        httpresponse.headers().set(HttpHeaderNames.CACHE_CONTROL, "no-cache, no-store, must-revalidate, max-age=0");
        httpresponse.headers().set(HttpHeaderNames.PRAGMA, "no-cache");
        httpresponse.headers().set(HttpHeaderNames.EXPIRES, "0");

        response.headers().entrySet().stream().forEach((e) -> httpresponse.headers().set(e.getKey(), e.getValue()));

        httpresponse.headers().set(HttpHeaderNames.SERVER, "Fuga Netty Web Server/" + serverVersion);

        // Set cookies
        httpresponse.headers().set(HttpHeaderNames.SET_COOKIE,
                ServerCookieEncoder.STRICT.encode(NettyCookieConverter.convertListToNetty(response.cookies())));

        if (response.length() >= 0) {
            httpresponse.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.length());
        }

        if (HttpUtil.isKeepAlive(httprequest)) {
            httpresponse.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
        } else {
            httpresponse.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
        }

        ctx.write(httpresponse);

        if (response.stream() != null) {
            ctx.write(new HttpChunkedInput(new ChunkedStream(response.stream())));
        }

        LastHttpContent fs = new DefaultLastHttpContent();
        ChannelFuture sendContentFuture = ctx.writeAndFlush(fs);
        if (!HttpUtil.isKeepAlive(httprequest)) {
            sendContentFuture.addListener(ChannelFutureListener.CLOSE);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.catching(cause);
        ctx.close();
        reset();
    }

    private static InetSocketAddress parseAddress(String addr) {
        String ip = addr.contains(":") ? addr.substring(0, addr.lastIndexOf(':')) : addr;
        String port = addr.contains(":") ? addr.substring(addr.lastIndexOf(':') + 1) : "0";
        return new InetSocketAddress(ip, Integer.parseInt(port));
    }

}