ozy.server.ServerHandler.java Source code

Java tutorial

Introduction

Here is the source code for ozy.server.ServerHandler.java

Source

/*
 * Copyright (c) 2016 Catavolt Inc. All rights reserved.
 *
 * This file is part of Ozy.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package ozy.server;

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.DecoderResult;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.cookie.Cookie;
import io.netty.handler.codec.http.cookie.ServerCookieDecoder;
import io.netty.handler.codec.http.cookie.ServerCookieEncoder;
import io.netty.util.CharsetUtil;
import ozy.fp.Future;
import ozy.klvm.ServiceRequest;
import ozy.klvm.ServiceResponse;
import ozy.util.ExceptionUtil;

import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import static io.netty.handler.codec.http.HttpHeaders.Names.*;
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.INTERNAL_SERVER_ERROR;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;

/**
 * This class was adapted from the Netty example at:
 * http://netty.io/4.0/xref/io/netty/example/http/snoop/HttpSnoopServerHandler.html
 */
class ServerHandler extends SimpleChannelInboundHandler<Object> {

    private HttpRequest _httpRequest;
    private final StringBuilder _httpContentBuf = new StringBuilder();
    private Server _server;
    private Future<ServiceResponse> _serverResponseFr;
    private ServiceRequest _serviceRequest;

    //--------------------------------------------------------------------------
    // CLASS METHODS
    //--------------------------------------------------------------------------

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

    //--------------------------------------------------------------------------
    // CONSTRUCTORS
    //--------------------------------------------------------------------------

    ServerHandler(Server server) {
        _server = server;
    }

    //--------------------------------------------------------------------------
    // INSTANCE METHODS
    //--------------------------------------------------------------------------

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

    @Override
    public final void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        sendErrorMessage(ctx, INTERNAL_SERVER_ERROR, cause);
    }

    public final Router router() {
        return _server.router();
    }

    // ~~~~~~~~~~~~~~~~~~~~~~~~~ PROTECTED ~~~~~~~~~~~~~~~~~~~~~~~~~ //

    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        super.channelActive(ctx);
    }

    @Override
    protected final void channelRead0(ChannelHandlerContext ctx, Object msg) {

        if (msg instanceof HttpObject) {
            HttpObject object = (HttpObject) msg;
            DecoderResult result = object.getDecoderResult();
            if (result.isFailure()) {
                sendErrorMessage(ctx, INTERNAL_SERVER_ERROR, result.cause());
                return;
            }
        }

        if (msg instanceof HttpRequest) {
            _serviceRequest = new ServiceRequest();
            _httpRequest = (HttpRequest) msg;
            _httpContentBuf.setLength(0);
            if (HttpHeaders.is100ContinueExpected(_httpRequest)) {
                send100Continue(ctx);
            }
            receiveHeader();
        }

        if (msg instanceof HttpContent) {
            HttpContent next = (HttpContent) msg;
            receiveContent(next);
            if (next instanceof LastHttpContent) {
                LastHttpContent last = (LastHttpContent) next;
                receiveTrailer(last);
                routeRequest(ctx);
            }
        }

    }

    // ~~~~~~~~~~~~~~~~~~~~~~~~~ PRIVATE ~~~~~~~~~~~~~~~~~~~~~~~~~ //

    private void encodeCookies(ChannelHandlerContext ctx, FullHttpResponse response) {
        String cookieString = _httpRequest.headers().get(COOKIE);
        if (cookieString != null) {
            Set<Cookie> cookies = ServerCookieDecoder.LAX.decode(cookieString);
            if (!cookies.isEmpty()) {
                for (Cookie cookie : cookies) {
                    response.headers().add(SET_COOKIE, ServerCookieEncoder.LAX.encode(cookie));
                }
            }
        }
    }

    private void receiveHeader() {
        QueryStringDecoder queryStringDecoder = new QueryStringDecoder(_httpRequest.getUri());
        _serviceRequest.setMethod(_httpRequest.getMethod().toString());
        _serviceRequest.setSenderHost(HttpHeaders.getHost(_httpRequest, "unknown"));
        _serviceRequest.setPath(queryStringDecoder.path());
        HttpHeaders headers = _httpRequest.headers();
        if (!headers.isEmpty()) {
            for (Map.Entry<String, String> h : headers) {
                _serviceRequest.headers().put(h.getKey(), h.getValue());
            }
        }
        Map<String, List<String>> params = queryStringDecoder.parameters();
        if (!params.isEmpty()) {
            for (Entry<String, List<String>> p : params.entrySet()) {
                _serviceRequest.params().put(p.getKey(), p.getValue());
            }
        }
    }

    private void receiveContent(HttpContent content) {
        ByteBuf byteContent = content.content();
        if (byteContent.isReadable()) {
            _httpContentBuf.append(byteContent.toString(CharsetUtil.UTF_8));
        }
    }

    private void receiveTrailer(LastHttpContent trailer) {
        _serviceRequest.setBody(_httpContentBuf.toString());
        HttpHeaders headers = trailer.trailingHeaders();
        if (!headers.isEmpty()) {
            for (Map.Entry<String, String> h : headers) {
                _serviceRequest.trailingHeaders().put(h.getKey(), h.getValue());
            }
        }
    }

    private void routeRequest(ChannelHandlerContext ctx) {
        if (router() == null) {
            sendErrorMessage(ctx, INTERNAL_SERVER_ERROR, new NullPointerException("router"));
        } else if (!router().isRouterFor(_serviceRequest)) {
            sendErrorMessage(ctx, BAD_REQUEST, "[ServerHandler] invalid request");
        } else {
            try {
                _serverResponseFr = router().route(_serviceRequest);
                _serverResponseFr.onComplete((responseTry) -> {
                    if (responseTry.isSuccess()) {
                        try {
                            sendResponse(ctx, responseTry.success());
                        } catch (Exception exc) {
                            sendErrorMessage(ctx, INTERNAL_SERVER_ERROR, exc);
                        }
                        if (!HttpHeaders.isKeepAlive(_httpRequest)) {
                            ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
                        }
                    } else {
                        sendErrorMessage(ctx, INTERNAL_SERVER_ERROR, responseTry.failure().toDebugString());
                    }
                });
            } catch (Exception exc) {
                sendErrorMessage(ctx, INTERNAL_SERVER_ERROR, exc);
            }
        }
    }

    private void sendErrorMessage(ChannelHandlerContext ctx, HttpResponseStatus status, String message) {
        ByteBuf responseBytes = Unpooled.copiedBuffer(message, CharsetUtil.UTF_8);
        FullHttpResponse httpResponse = new DefaultFullHttpResponse(HTTP_1_1, status, responseBytes);
        httpResponse.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8");
        if (HttpHeaders.isKeepAlive(_httpRequest)) {
            HttpHeaders.setContentLength(httpResponse, httpResponse.content().readableBytes());
            HttpHeaders.setKeepAlive(httpResponse, true);
        }
        encodeCookies(ctx, httpResponse);
        ctx.writeAndFlush(httpResponse).addListener(ChannelFutureListener.CLOSE);
    }

    private void sendErrorMessage(ChannelHandlerContext ctx, HttpResponseStatus status, Throwable cause) {
        String message = ExceptionUtil.formatAsString(cause);
        sendErrorMessage(ctx, status, message);
    }

    private void sendResponse(ChannelHandlerContext ctx, ServiceResponse serviceResponse) {
        ByteBuf responseBytes = Unpooled.copiedBuffer(serviceResponse.body(), CharsetUtil.UTF_8);
        HttpResponseStatus httpStatus = HttpResponseStatus.valueOf(serviceResponse.status());
        FullHttpResponse httpResponse = new DefaultFullHttpResponse(HTTP_1_1, httpStatus, responseBytes);
        httpResponse.headers().set(CONTENT_TYPE, serviceResponse.contentType());
        if (HttpHeaders.isKeepAlive(_httpRequest)) {
            httpResponse.headers().set(CONTENT_LENGTH, httpResponse.content().readableBytes());
            httpResponse.headers().set(CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
        }
        String cookieString = _httpRequest.headers().get(COOKIE);
        if (cookieString != null) {
            Set<Cookie> cookies = ServerCookieDecoder.LAX.decode(cookieString);
            if (!cookies.isEmpty()) {
                for (Cookie cookie : cookies) {
                    httpResponse.headers().add(SET_COOKIE, ServerCookieEncoder.LAX.encode(cookie));
                }
            }
            for (Map.Entry<String, String> c : serviceResponse.cookies().entrySet()) {
                httpResponse.headers().add(SET_COOKIE, ServerCookieEncoder.LAX.encode(c.getKey(), c.getValue()));
            }
        }
        ctx.write(httpResponse);
    }

}