Java tutorial
/* * 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; } }