您的当前位置:首页正文

HTTP协议处理

来源:花图问答

使用Netty服务开发。实现HTTP协议处理逻辑。

package 
  
import   
import   
import   
import   
  
/** 
 * http协议文件传输 
 * @author Qixuan.Chen 
 * 创建时间:2015年5月4日 
 */  
public class HttpStaticFileServer {  
  
      
    private final int port;//端口  
  
    public HttpStaticFileServer(int port) {  
        this.port = port;  
    }  
  
    public void run() throws Exception {  
        EventLoopGroup bossGroup = new NioEventLoopGroup();//线程一 //这个是用于serversocketchannel的event  
        EventLoopGroup workerGroup = new NioEventLoopGroup();//线程二//这个是用于处理accept到的channel  
        try {  
            ServerBootstrap b = new ServerBootstrap();  
            b.group(bossGroup, workerGroup)  
             .channel(NioServerSocketChannel.class)  
             .childHandler(new HttpStaticFileServerInitializer());  
  
            b.bind(port).sync().channel().closeFuture().sync();  
        } finally {  
            bossGroup.shutdownGracefully();  
            workerGroup.shutdownGracefully();  
        }  
    }  
  
    public static void main(String[] args) throws Exception {  
        int port = 8089;  
        if (args.length > 0) {  
            port = Integer.parseInt(args[0]);  
        } else {  
            port = 8089;  
        }  
        new HttpStaticFileServer(port).run();//启动服务  
    }  
}  
package 

import static 
import static 
import static 
import static 
import static 
import static 
import static 
import static 
import static 
import static 
import static 
import static 
import static 
import static 
import static 
import static 
import static 
import static 

import java.io.File;
import java.io.FileNotFoundException;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import 
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;
import java.util.regex.Pattern;

import javax.activation.MimetypesFileTypeMap;

import 
import 
import 
import 
import 
import 
import 
import 
import 
import 
import 
import 
import 
import 
import 
import 
import 
import 
import 
import   
  
/** 
 * A simple handler that serves incoming HTTP requests to send their respective 
 * HTTP responses.  It also implements {@code 'If-Modified-Since'} header to 
 * take advantage of browser cache, as described in 
 * <a href="http://tools.ietf.org/html/rfc2616#section-14.25">RFC 2616</a>. 
 * 
 * <h3>How Browser Caching Works</h3> 
 * 
 * Web browser caching works with HTTP headers as illustrated by the following 
 * sample: 
 * <ol> 
 * <li>Request #1 returns the content of {@code /file1.txt}.</li> 
 * <li>Contents of {@code /file1.txt} is cached by the browser.</li> 
 * <li>Request #2 for {@code /file1.txt} does return the contents of the 
 *     file again. Rather, a 304 Not Modified is returned. This tells the 
 *     browser to use the contents stored in its cache.</li> 
 * <li>The server knows the file has not been modified because the 
 *     {@code If-Modified-Since} date is the same as the file's last 
 *     modified date.</li> 
 * </ol> 
 * 
 * <pre> 
 * Request #1 Headers 
 * =================== 
 * GET /file1.txt HTTP/1.1 
 * 
 * Response #1 Headers 
 * =================== 
 * HTTP/1.1 200 OK 
 * Date:               Tue, 01 Mar 2011 22:44:26 GMT 
 * Last-Modified:      Wed, 30 Jun 2010 21:36:48 GMT 
 * Expires:            Tue, 01 Mar 2012 22:44:26 GMT 
 * Cache-Control:      private, max-age=31536000 
 * 
 * Request #2 Headers 
 * =================== 
 * GET /file1.txt HTTP/1.1 
 * If-Modified-Since:  Wed, 30 Jun 2010 21:36:48 GMT 
 * 
 * Response #2 Headers 
 * =================== 
 * HTTP/1.1 304 Not Modified 
 * Date:               Tue, 01 Mar 2011 22:44:28 GMT 
 * 
 * </pre> 
 */  
public class HttpStaticFileServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {  
  
    public static final String HTTP_DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss zzz";  
    public static final String HTTP_DATE_GMT_TIMEZONE = "GMT";  
    public static final int HTTP_CACHE_SECONDS = 60;  
  
    private final boolean useSendFile;  
  
    public HttpStaticFileServerHandler(boolean useSendFile) {  
        this.useSendFile = useSendFile;  
    }  
  
    /**
     * 类似channelRead方法。
     */
    @Override  
    public void messageReceived(  
            ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {  
        if (!request.decoderResult().isSuccess()) {  
            sendError(ctx, BAD_REQUEST);  
            return;  
        }  
  
        if (request.method() != GET) {  
            sendError(ctx, METHOD_NOT_ALLOWED);  
            return;  
        }  
  
        final String uri = request.uri();  
        System.out.println("-----uri----"+uri);  
        final String path = sanitizeUri(uri);  
        System.out.println("-----path----"+path);  
        if (path == null) {  
            sendError(ctx, FORBIDDEN);  
            return;  
        }  
  
        File file = new File(path);  
        if (file.isHidden() || !file.exists()) {  
            sendError(ctx, NOT_FOUND);  
            return;  
        }  
  
        if (file.isDirectory()) {  
            if (uri.endsWith("/")) {  
                sendListing(ctx, file);  
            } else {  
                sendRedirect(ctx, uri + '/');  
            }  
            return;  
        }  
  
        if (!file.isFile()) {  
            sendError(ctx, FORBIDDEN);  
            return;  
        }  
  
        // Cache Validation  
        String ifModifiedSince = (String) request.headers().get(IF_MODIFIED_SINCE);  
        if (ifModifiedSince != null && !ifModifiedSince.isEmpty()) {  
            SimpleDateFormat dateFormatter = new SimpleDateFormat(HTTP_DATE_FORMAT, Locale.US);  
            Date ifModifiedSinceDate = dateFormatter.parse(ifModifiedSince);  
  
            // Only compare up to the second because the datetime format we send to the client  
            // does not have milliseconds  
            long ifModifiedSinceDateSeconds = ifModifiedSinceDate.getTime() / 1000;  
            long fileLastModifiedSeconds = file.lastModified() / 1000;  
            if (ifModifiedSinceDateSeconds == fileLastModifiedSeconds) {  
                sendNotModified(ctx);  
                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);  
        HttpHeaderUtil.setContentLength(response, fileLength);
        setContentTypeHeader(response, file);  
        setDateAndCacheHeaders(response, file);  
        if (HttpHeaderUtil.isKeepAlive(request)) {  
            response.headers().set(CONNECTION, HttpHeaderValues.KEEP_ALIVE);  
        }  
  
        // Write the initial line and the header.  
        ctx.write(response);  
  
        // Write the content.  
        ChannelFuture sendFileFuture;  
        if (useSendFile) {  
            sendFileFuture =  
                    ctx.write(new DefaultFileRegion(raf.getChannel(), 0, fileLength), ctx.newProgressivePromise());  
        } else {  
            sendFileFuture =  
                    ctx.write(new ChunkedFile(raf, 0, fileLength, 8192), ctx.newProgressivePromise());  
        }  
  
        sendFileFuture.addListener(new ChannelProgressiveFutureListener() {  
            @Override  
            public void operationProgressed(ChannelProgressiveFuture future, long progress, long total) {  
                if (total < 0) { // total unknown  
                    System.err.println("Transfer progress: " + progress);  
                } else {  
                    System.err.println("Transfer progress: " + progress + " / " + total);  
                }  
            }  
  
            @Override  
            public void operationComplete(ChannelProgressiveFuture future) throws Exception {  
                System.err.println("Transfer complete.");  
            }  
        });  
  
        // Write the end marker  
        ChannelFuture lastContentFuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);  
  
        // Decide whether to close the connection or not.  
        if (!HttpHeaderUtil.isKeepAlive(request)) {  
            // Close the connection when the whole content is written out.  
            lastContentFuture.addListener(ChannelFutureListener.CLOSE);  
        }  
    }  
  
    @Override  
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {  
        cause.printStackTrace();  
        if (ctx.channel().isActive()) {  
            sendError(ctx, INTERNAL_SERVER_ERROR);  
        }  
    }  
  
    private static final Pattern INSECURE_URI =   
  
    /** 
     * 路径解码 
     * @param uri 
     * @return 
     */  
    private static String sanitizeUri(String uri) {  
        // Decode the path.  
        try {  
            uri = URLDecoder.decode(uri, "UTF-8");  
        } catch (UnsupportedEncodingException e) {  
            try {  
                uri = URLDecoder.decode(uri, "ISO-8859-1");  
            } catch (UnsupportedEncodingException e1) {  
                throw new Error();  
            }  
        }  
  
        if (!uri.startsWith("/")) {  
            return null;  
        }  
  
        // Convert file separators.  
        uri = uri.replace('/', File.separatorChar);  
  
        // Simplistic dumb security check.  
        // You will have to do something serious in the production environment.  
        if (uri.contains(File.separator + '.') ||  
            uri.contains('.' + File.separator) ||  
            uri.startsWith(".") || uri.endsWith(".") ||  
            INSECURE_URI.matcher(uri).matches()) {  
            return null;  
        }  
  
        // Convert to absolute path.  
        return System.getProperty("user.dir") + File.separator + uri;  
    }  
  
    private static final Pattern ALLOWED_FILE_NAME =   
  
    private static void sendListing(ChannelHandlerContext ctx, File dir) {  
        FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK);  
        response.headers().set(CONTENT_TYPE, "text/html; charset=UTF-8");  
  
        StringBuilder buf = new StringBuilder();  
        String dirPath = dir.getPath();  
  
        buf.append("<!DOCTYPE html>\r\n");  
        buf.append("<html><head><title>");  
        buf.append("Listing of: ");  
        buf.append(dirPath);  
        buf.append("</title></head><body>\r\n");  
  
        buf.append("<h3>Listing of: ");  
        buf.append(dirPath);  
        buf.append("</h3>\r\n");  
  
        buf.append("<ul>");  
        buf.append("<li><a href=\"../\">..</a></li>\r\n");  
  
        for (File f: dir.listFiles()) {  
            if (f.isHidden() || !f.canRead()) {  
                continue;  
            }  
  
            String name = f.getName();  
            if (!ALLOWED_FILE_NAME.matcher(name).matches()) {  
                continue;  
            }  
  
            buf.append("<li><a href=\"");  
            buf.append(name);  
            buf.append("\">");  
            buf.append(name);  
            buf.append("</a></li>\r\n");  
        }  
  
        buf.append("</ul></body></html>\r\n");  
        ByteBuf buffer = Unpooled.copiedBuffer(buf, CharsetUtil.UTF_8);  
        response.content().writeBytes(buffer);  
        buffer.release();  
  
        // Close the connection as soon as the error message is sent.  
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);  
    }  
  
    private static void sendRedirect(ChannelHandlerContext ctx, String newUri) {  
        FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, FOUND);  
        response.headers().set(LOCATION, newUri);  
  
        // Close the connection as soon as the error message is sent.  
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);  
    }  
  
    private static void sendError(ChannelHandlerContext ctx, HttpResponseStatus status) {  
        FullHttpResponse response = new DefaultFullHttpResponse(  
                HTTP_1_1, status, Unpooled.copiedBuffer("Failure: " + status.toString() + "\r\n", CharsetUtil.UTF_8));  
        response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8");  
  
        // Close the connection as soon as the error message is sent.  
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);  
    }  
  
    /** 
     * When file timestamp is the same as what the browser is sending up, send a "304 Not Modified" 
     * 
     * @param ctx 
     *            Context 
     */  
    private static void sendNotModified(ChannelHandlerContext ctx) {  
        FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, NOT_MODIFIED);  
        setDateHeader(response);  
  
        // Close the connection as soon as the error message is sent.  
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);  
    }  
  
    /** 
     * Sets the Date header for the HTTP response 
     * 
     * @param response 
     *            HTTP response 
     */  
    private static void setDateHeader(FullHttpResponse response) {  
        SimpleDateFormat dateFormatter = new SimpleDateFormat(HTTP_DATE_FORMAT, Locale.US);  
        dateFormatter.setTimeZone(TimeZone.getTimeZone(HTTP_DATE_GMT_TIMEZONE));  
  
        Calendar time = new GregorianCalendar();  
        response.headers().set(DATE, dateFormatter.format(time.getTime()));  
    }  
  
    /** 
     * Sets the Date and Cache headers for the HTTP Response 
     * 
     * @param response 
     *            HTTP response 
     * @param fileToCache 
     *            file to extract content type 
     */  
    private static void setDateAndCacheHeaders(HttpResponse response, File fileToCache) {  
        SimpleDateFormat dateFormatter = new SimpleDateFormat(HTTP_DATE_FORMAT, Locale.US);  
        dateFormatter.setTimeZone(TimeZone.getTimeZone(HTTP_DATE_GMT_TIMEZONE));  
  
        // Date header  
        Calendar time = new GregorianCalendar();  
        response.headers().set(DATE, dateFormatter.format(time.getTime()));  
  
        // Add cache headers  
        time.add(Calendar.SECOND, HTTP_CACHE_SECONDS);  
        response.headers().set(EXPIRES, dateFormatter.format(time.getTime()));  
        response.headers().set(CACHE_CONTROL, "private, max-age=" + HTTP_CACHE_SECONDS);  
        response.headers().set(  
                LAST_MODIFIED, dateFormatter.format(new Date(fileToCache.lastModified())));  
    }  
  
    /** 
     * Sets the content type header for the HTTP Response 
     * 
     * @param response 
     *            HTTP response 
     * @param file 
     *            file to extract content type 
     */  
    private static void setContentTypeHeader(HttpResponse response, File file) {  
        MimetypesFileTypeMap mimeTypesMap = new MimetypesFileTypeMap();  
        response.headers().set(CONTENT_TYPE, mimeTypesMap.getContentType(file.getPath()));  
    }  
  
}  
package 

import   
import   
import   
import   
import   
import   
import   
  
public class HttpStaticFileServerInitializer extends ChannelInitializer<SocketChannel> {  
    @Override  
    public void initChannel(SocketChannel ch) throws Exception {  
        // Create a default pipeline implementation.  
        ChannelPipeline pipeline = ch.pipeline();  
  
        // Uncomment the following line if you want HTTPS  
        //SSLEngine engine = SecureChatSslContextFactory.getServerContext().createSSLEngine();  
        //engine.setUseClientMode(false);  
        //pipeline.addLast("ssl", new SslHandler(engine));  
       /** 
        *   (1)ReadTimeoutHandler,用于控制读取数据的时候的超时,10表示如果10秒钟都没有数据读取了,那么就引发超时,然后关闭当前的channel 
 
            (2)WriteTimeoutHandler,用于控制数据输出的时候的超时,构造参数1表示如果持续1秒钟都没有数据写了,那么就超时。 
             
            (3)HttpRequestrianDecoder,这个handler用于从读取的数据中将http报文信息解析出来,无非就是什么requestline,header,body什么的。。。 
             
            (4)然后HttpObjectAggregator则是用于将上卖解析出来的http报文的数据组装成为封装好的httprequest对象。。 
             
            (5)HttpresponseEncoder,用于将用户返回的httpresponse编码成为http报文格式的数据 
             
            (6)HttpHandler,自定义的handler,用于处理接收到的http请求。 
        */  
          
        pipeline.addLast("decoder", new HttpRequestDecoder());// http-request解码器,http服务器端对request解码  
        pipeline.addLast("aggregator", new HttpObjectAggregator(65536));//对传输文件大少进行限制  
        pipeline.addLast("encoder", new HttpResponseEncoder());//http-response解码器,http服务器端对response编码  
        // 向客户端发送数据的一个Handler
        pipeline.addLast("chunkedWriter", new ChunkedWriteHandler());  
  
        pipeline.addLast("handler", new HttpStaticFileServerHandler(true)); // Specify false if SSL.(如果是ssl,就指定为false)  
    }  
}