org.ratpackframework.server.internal.NettyHandlerAdapter.java Source code

Java tutorial

Introduction

Here is the source code for org.ratpackframework.server.internal.NettyHandlerAdapter.java

Source

/*
 * Copyright 2013 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 org.ratpackframework.server.internal;

import com.google.common.util.concurrent.ListeningExecutorService;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;
import org.ratpackframework.error.ClientErrorHandler;
import org.ratpackframework.error.ServerErrorHandler;
import org.ratpackframework.error.internal.DefaultClientErrorHandler;
import org.ratpackframework.error.internal.DefaultServerErrorHandler;
import org.ratpackframework.error.internal.ErrorCatchingHandler;
import org.ratpackframework.file.FileRenderer;
import org.ratpackframework.file.FileSystemBinding;
import org.ratpackframework.file.MimeTypes;
import org.ratpackframework.file.internal.*;
import org.ratpackframework.handling.Context;
import org.ratpackframework.handling.Handler;
import org.ratpackframework.handling.Redirector;
import org.ratpackframework.handling.internal.ClientErrorForwardingHandler;
import org.ratpackframework.handling.internal.DefaultContext;
import org.ratpackframework.handling.internal.DefaultRedirector;
import org.ratpackframework.http.MutableHeaders;
import org.ratpackframework.http.Request;
import org.ratpackframework.http.Response;
import org.ratpackframework.http.internal.*;
import org.ratpackframework.launch.LaunchConfig;
import org.ratpackframework.registry.Registry;
import org.ratpackframework.registry.RegistryBuilder;
import org.ratpackframework.server.BindAddress;
import org.ratpackframework.server.PublicAddress;

import java.io.IOException;
import java.net.InetSocketAddress;

import static io.netty.handler.codec.http.HttpHeaders.isKeepAlive;
import static io.netty.handler.codec.http.HttpResponseStatus.NOT_FOUND;

@ChannelHandler.Sharable
public class NettyHandlerAdapter extends SimpleChannelInboundHandler<FullHttpRequest> {

    private final Handler handler;
    private final Handler return404;
    private final LaunchConfig launchConfig;
    private final ListeningExecutorService blockingExecutorService;

    private Registry registry;

    public NettyHandlerAdapter(Handler handler, LaunchConfig launchConfig,
            ListeningExecutorService blockingExecutorService) {
        this.launchConfig = launchConfig;
        this.blockingExecutorService = blockingExecutorService;
        this.handler = new ErrorCatchingHandler(handler);
        this.return404 = new ClientErrorForwardingHandler(NOT_FOUND.code());
    }

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        registry = createRegistry(ctx.channel());
        super.channelRegistered(ctx);
    }

    private Registry createRegistry(Channel channel) {
        InetSocketAddress socketAddress = (InetSocketAddress) channel.localAddress();

        BindAddress bindAddress = new InetSocketAddressBackedBindAddress(socketAddress);
        String uriScheme = launchConfig.getSSLContext() == null ? "http" : "https";

        // If you update this list, update the class level javadoc on Context.
        return RegistryBuilder.builder()
                .add(FileSystemBinding.class, new DefaultFileSystemBinding(launchConfig.getBaseDir()))
                .add(MimeTypes.class, new ActivationBackedMimeTypes()).add(BindAddress.class, bindAddress)
                .add(PublicAddress.class,
                        new DefaultPublicAddress(launchConfig.getPublicAddress(), uriScheme, bindAddress))
                .add(Redirector.class, new DefaultRedirector())
                .add(ClientErrorHandler.class, new DefaultClientErrorHandler())
                .add(ServerErrorHandler.class, new DefaultServerErrorHandler())
                .add(LaunchConfig.class, launchConfig).add(FileRenderer.class, new DefaultFileRenderer()).build();
    }

    public void channelRead0(final ChannelHandlerContext ctx, final FullHttpRequest nettyRequest) throws Exception {
        if (!nettyRequest.getDecoderResult().isSuccess()) {
            sendError(ctx, HttpResponseStatus.BAD_REQUEST);
            return;
        }

        final FullHttpResponse nettyResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
                HttpResponseStatus.OK);

        Request request = new DefaultRequest(new NettyHeadersBackedHeaders(nettyRequest.headers()),
                nettyRequest.getMethod().name(), nettyRequest.getUri(), nettyRequest.content());

        final Channel channel = ctx.channel();

        final DefaultStatus responseStatus = new DefaultStatus();
        final MutableHeaders responseHeaders = new NettyHeadersBackedMutableHeaders(nettyResponse.headers());
        final ByteBuf responseBody = nettyResponse.content();
        FileHttpTransmitter fileHttpTransmitter = new DefaultFileHttpTransmitter(nettyRequest, nettyResponse,
                channel);

        Response response = new DefaultResponse(responseStatus, responseHeaders, responseBody, fileHttpTransmitter,
                new Runnable() {
                    @Override
                    public void run() {
                        nettyResponse.setStatus(responseStatus.getResponseStatus());
                        responseHeaders.set(HttpHeaders.Names.CONTENT_LENGTH, responseBody.writerIndex());
                        boolean shouldClose = true;
                        if (channel.isOpen()) {
                            if (isKeepAlive(nettyRequest)) {
                                responseHeaders.set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
                                shouldClose = false;
                            }
                            ChannelFuture future = channel.writeAndFlush(nettyResponse);
                            if (shouldClose) {
                                future.addListener(ChannelFutureListener.CLOSE);
                            }
                        }
                    }
                });

        if (registry == null) {
            throw new IllegalStateException("Registry is not set, channel is not registered");
        }

        final Context context = new DefaultContext(request, response, registry, ctx.executor(),
                blockingExecutorService, return404);

        handler.handle(context);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        if (!isIgnorableException(cause)) {
            cause.printStackTrace();
            if (ctx.channel().isActive()) {
                sendError(ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR);
            }
        }
    }

    private boolean isIgnorableException(Throwable throwable) {
        // There really does not seem to be a better way of detecting this kind of exception
        return throwable instanceof IOException && throwable.getMessage().equals("Connection reset by peer");
    }

    private static void sendError(ChannelHandlerContext ctx, HttpResponseStatus status) {
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status,
                Unpooled.copiedBuffer("Failure: " + status.toString() + "\r\n", CharsetUtil.UTF_8));
        response.headers().set(HttpHeaders.Names.CONTENT_TYPE, "text/plain; charset=UTF-8");

        // Close the connection as soon as the error message is sent.
        ctx.write(response).addListener(ChannelFutureListener.CLOSE);
    }
}