net.javaforge.netty.servlet.bridge.ServletBridgeHandler.java Source code

Java tutorial

Introduction

Here is the source code for net.javaforge.netty.servlet.bridge.ServletBridgeHandler.java

Source

/*
 * Copyright 2013 by Maxim Kalina
 *
 *    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 net.javaforge.netty.servlet.bridge;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.handler.codec.TooLongFrameException;
import io.netty.handler.codec.http.*;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.stream.ChunkedFile;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
import net.javaforge.netty.servlet.bridge.impl.FilterChainImpl;
import net.javaforge.netty.servlet.bridge.impl.HttpServletRequestImpl;
import net.javaforge.netty.servlet.bridge.impl.HttpServletResponseImpl;
import net.javaforge.netty.servlet.bridge.impl.ServletBridgeWebapp;
import net.javaforge.netty.servlet.bridge.util.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;

import static io.netty.handler.codec.http.HttpHeaders.Names.*;
import static io.netty.handler.codec.http.HttpHeaders.setContentLength;
import static io.netty.handler.codec.http.HttpMethod.GET;
import static io.netty.handler.codec.http.HttpResponseStatus.*;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;

@ChannelHandler.Sharable
public class ServletBridgeHandler extends IdleStateHandler {

    private static final Logger log = LoggerFactory.getLogger(ServletBridgeHandler.class);

    private List<ServletBridgeInterceptor> interceptors;

    /**
     * Which uri should be passed into this servlet container
     */
    private String uriPrefix = "/";

    public ServletBridgeHandler() {
        this("/");
    }

    public ServletBridgeHandler(String uriPrefix) {
        super(20000, 20000, 20000);
        this.uriPrefix = uriPrefix;
    }

    public ServletBridgeHandler addInterceptor(ServletBridgeInterceptor interceptor) {

        if (interceptors == null)
            interceptors = new ArrayList<ServletBridgeInterceptor>();

        interceptors.add(interceptor);
        return this;
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        log.debug("Opening new channel: {}", ctx.channel().id());
        ServletBridgeWebapp.get().getSharedChannelGroup().add(ctx.channel());

        ctx.fireChannelActive();
    }

    @Override
    public void channelIdle(ChannelHandlerContext ctx, IdleStateEvent e) {
        log.debug("Closing idle channel: {}", ctx.channel().id());
        ctx.channel().close();
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object e) throws Exception {

        if (e instanceof HttpRequest) {
            HttpRequest request = (HttpRequest) e;

            String uri = request.uri();

            if (uri.startsWith(uriPrefix)) {
                if (HttpHeaders.is100ContinueExpected(request)) {
                    ctx.channel().write(new DefaultHttpResponse(HTTP_1_1, CONTINUE));
                }

                FilterChainImpl chain = ServletBridgeWebapp.get().initializeChain(uri);

                if (chain.isValid()) {
                    handleHttpServletRequest(ctx, request, chain);
                } else if (ServletBridgeWebapp.get().getStaticResourcesFolder() != null) {
                    handleStaticResourceRequest(ctx, request);
                } else {
                    throw new ServletBridgeRuntimeException("No handler found for uri: " + request.getUri());
                }
            } else {
                ctx.fireChannelRead(e);
            }
        } else {
            ctx.fireChannelRead(e);
        }
    }

    protected void handleHttpServletRequest(ChannelHandlerContext ctx, HttpRequest request, FilterChainImpl chain)
            throws Exception {

        interceptOnRequestReceived(ctx, request);

        DefaultFullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK);

        HttpServletResponseImpl resp = buildHttpServletResponse(response);
        HttpServletRequestImpl req = buildHttpServletRequest(request, chain);

        chain.doFilter(req, resp);

        interceptOnRequestSuccessed(ctx, request, response);

        resp.getWriter().flush();

        boolean keepAlive = HttpHeaders.isKeepAlive(request);

        if (keepAlive) {

            // Add 'Content-Length' header only for a keep-alive connection.
            response.headers().set(CONTENT_LENGTH, response.content().readableBytes());
            // Add keep alive header as per:
            // -
            // http://www.w3.org/Protocols/HTTP/1.1/draft-ietf-http-v11-spec-01.html#Connection
            response.headers().set(CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
        }

        // write response...
        ChannelFuture future = ctx.channel().writeAndFlush(response);

        if (!keepAlive) {
            future.addListener(ChannelFutureListener.CLOSE);
        }

    }

    protected void handleStaticResourceRequest(ChannelHandlerContext ctx, HttpRequest request) throws Exception {
        if (request.method() != GET) {
            sendError(ctx, METHOD_NOT_ALLOWED);
            return;
        }

        String uri = Utils.sanitizeUri(request.uri());
        final String path = (uri != null
                ? ServletBridgeWebapp.get().getStaticResourcesFolder().getAbsolutePath() + File.separator + uri
                : null);

        if (path == null) {
            sendError(ctx, FORBIDDEN);
            return;
        }

        File file = new File(path);
        if (file.isHidden() || !file.exists()) {
            sendError(ctx, NOT_FOUND);
            return;
        }
        if (!file.isFile()) {
            sendError(ctx, FORBIDDEN);
            return;
        }

        RandomAccessFile raf;
        try {
            raf = new RandomAccessFile(file, "r");
        } catch (FileNotFoundException fnfe) {
            sendError(ctx, NOT_FOUND);
            return;
        }

        long fileLength = raf.length();

        HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK);
        setContentLength(response, fileLength);

        Channel ch = ctx.channel();

        // Write the initial line and the header.
        ch.write(response);

        // Write the content.
        ChannelFuture writeFuture;
        if (isSslChannel(ch)) {
            // Cannot use zero-copy with HTTPS.
            writeFuture = ch.write(new ChunkedFile(raf, 0, fileLength, 8192));
        } else {
            // No encryption - use zero-copy.
            final FileRegion region = new DefaultFileRegion(raf.getChannel(), 0, fileLength);
            writeFuture = ch.write(region);
            writeFuture.addListener(new ChannelProgressiveFutureListener() {

                @Override
                public void operationProgressed(ChannelProgressiveFuture channelProgressiveFuture, long current,
                        long total) throws Exception {
                    System.out.printf("%s: %d / %d (+%d)%n", path, current, total, total);
                }

                @Override
                public void operationComplete(ChannelProgressiveFuture channelProgressiveFuture) throws Exception {
                    region.release();
                }
            });
        }

    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        log.error("Unexpected exception from downstream.", cause);

        Channel ch = ctx.channel();
        if (cause instanceof IllegalArgumentException) {
            ch.close();
        } else {
            if (cause instanceof TooLongFrameException) {
                sendError(ctx, BAD_REQUEST);
                return;
            }

            if (ch.isActive()) {
                sendError(ctx, INTERNAL_SERVER_ERROR);
            }

        }

    }

    private void sendError(ChannelHandlerContext ctx, HttpResponseStatus status) {
        String text = "Failure: " + status.toString() + "\r\n";
        ByteBuf byteBuf = Unpooled.buffer();
        byte[] bytes = null;
        try {
            bytes = text.getBytes("utf-8");
            byteBuf.writeBytes(bytes);
        } catch (UnsupportedEncodingException e) {
        }

        FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK, byteBuf);
        HttpHeaders headers = response.headers();

        headers.add(CONTENT_TYPE, "text/plain;charset=utf-8");
        headers.add(CACHE_CONTROL, "no-cache");
        headers.add(PRAGMA, "No-cache");
        headers.add(SERVER, "eBay Server");
        headers.add(CONTENT_LENGTH, byteBuf.readableBytes());
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }

    private void interceptOnRequestReceived(ChannelHandlerContext ctx, HttpRequest request) {
        if (interceptors != null) {
            for (ServletBridgeInterceptor interceptor : interceptors) {
                interceptor.onRequestReceived(ctx, request);
            }
        }

    }

    private void interceptOnRequestSuccessed(ChannelHandlerContext ctx, HttpRequest request,
            HttpResponse response) {
        if (interceptors != null) {
            for (ServletBridgeInterceptor interceptor : interceptors) {
                interceptor.onRequestSuccessed(ctx, request, response);
            }
        }

    }

    // private void interceptOnRequestFailed(ChannelHandlerContext ctx,
    // ExceptionEvent e, HttpResponse response) {
    // if (this.interceptors != null) {
    // for (ServletBridgeInterceptor interceptor : this.interceptors) {
    // interceptor.onRequestFailed(ctx, e, response);
    // }
    // }
    //
    // }

    protected HttpServletResponseImpl buildHttpServletResponse(FullHttpResponse response) {
        return new HttpServletResponseImpl(response);
    }

    protected HttpServletRequestImpl buildHttpServletRequest(HttpRequest request, FilterChainImpl chain) {
        return new HttpServletRequestImpl(request, chain);
    }

    private boolean isSslChannel(Channel ch) {
        return ch.pipeline().get(SslHandler.class) != null;
    }

    public String getUriPrefix() {
        return uriPrefix;
    }

    public void setUriPrefix(String uriPrefix) {
        this.uriPrefix = uriPrefix;
    }
}